Übergeben Sie die Methode als Parameter mit C #

694

Ich habe mehrere Methoden, alle mit derselben Signatur (Parameter und Rückgabewerte), aber unterschiedliche Namen und Interna der Methoden sind unterschiedlich. Ich möchte den Namen der Methode, die ausgeführt werden soll, an eine andere Methode übergeben, die die übergebene Methode aufruft.

public int Method1(string)
{
    ... do something
    return myInt;
}

public int Method2(string)
{
    ... do something different
    return myInt;
}

public bool RunTheMethod([Method Name passed in here] myMethodName)
{
    ... do stuff
    int i = myMethodName("My String");
    ... do more stuff
    return true;
}

public bool Test()
{
    return RunTheMethod(Method1);
}

Dieser Code funktioniert nicht, aber ich versuche dies zu tun. Was ich nicht verstehe, ist, wie man den RunTheMethod-Code schreibt, da ich den Parameter definieren muss.

user31673
quelle
12
Warum übergeben Sie nicht einen Delegaten anstelle des Namens der Methode?
Mark Byers

Antworten:

852

Sie können den Func-Delegaten in .net 3.5 als Parameter in Ihrer RunTheMethod-Methode verwenden. Mit dem Func-Delegaten können Sie eine Methode angeben, die eine Reihe von Parametern eines bestimmten Typs verwendet und ein einzelnes Argument eines bestimmten Typs zurückgibt. Hier ist ein Beispiel, das funktionieren sollte:

public class Class1
{
    public int Method1(string input)
    {
        //... do something
        return 0;
    }

    public int Method2(string input)
    {
        //... do something different
        return 1;
    }

    public bool RunTheMethod(Func<string, int> myMethodName)
    {
        //... do stuff
        int i = myMethodName("My String");
        //... do more stuff
        return true;
    }

    public bool Test()
    {
        return RunTheMethod(Method1);
    }
}
Egil Hansen
quelle
51
Wie würde sich der Func-Aufruf ändern, wenn die Methode die Signatur für die Rückgabe von void und keine Parameter hat? Ich kann die Syntax anscheinend nicht zum Laufen bringen.
user31673
210
@unknown: In diesem Fall wäre es Actionstatt Func<string, int>.
Jon Skeet
12
aber was ist nun, wenn Sie Argumente an die Methode übergeben möchten?
John Ktejik
40
@ user396483 Entspricht beispielsweise Action<int,string>einer Methode, die 2 Parameter (int und string) verwendet und void zurückgibt .
Serdar
24
@NoelWidmer Using Func<double,string,int>entspricht einer Methode, die 2 Parameter ( doubleund string) akzeptiert und zurückgibt int. Der zuletzt angegebene Typ ist der Rückgabetyp. Sie können diesen Delegaten für bis zu 16 Parameter verwenden. Wenn Sie irgendwie mehr brauchen, schreiben Sie Ihren eigenen Delegierten als public delegate TResult Func<in T1, in T2, (as many arguments as you want), in Tn, out TResult>(T1 arg1, T2 arg2, ..., Tn argn);. Bitte korrigieren Sie mich, wenn ich falsch verstanden habe.
Serdar
356

Sie müssen einen Delegaten verwenden . In diesem Fall nehmen alle Ihre Methoden einen stringParameter und geben einen zurück int- dies wird am einfachsten durch den Func<string, int>Delegaten 1 dargestellt . So kann Ihr Code mit einer so einfachen Änderung wie folgt korrekt werden:

public bool RunTheMethod(Func<string, int> myMethodName)
{
    // ... do stuff
    int i = myMethodName("My String");
    // ... do more stuff
    return true;
}

Zugegeben, die Delegierten haben viel mehr Macht. Mit C # können Sie beispielsweise einen Delegaten aus einem Lambda-Ausdruck erstellen , sodass Sie Ihre Methode folgendermaßen aufrufen können:

RunTheMethod(x => x.Length);

Dadurch wird eine anonyme Funktion wie folgt erstellt:

// The <> in the name make it "unspeakable" - you can't refer to this method directly
// in your own code.
private static int <>_HiddenMethod_<>(string x)
{
    return x.Length;
}

und übergeben Sie diesen Delegaten dann an die RunTheMethodMethode.

Sie können Delegaten für Ereignisabonnements, asynchrone Ausführung, Rückrufe - alles Mögliche - verwenden. Es lohnt sich, sie zu lesen, insbesondere wenn Sie LINQ verwenden möchten. Ich habe einen Artikel, in dem es hauptsächlich um die Unterschiede zwischen Delegierten und Veranstaltungen geht, aber Sie finden ihn vielleicht trotzdem nützlich.


1 Dies basiert nur auf dem generischen Func<T, TResult>Delegatentyp im Framework. Sie könnten leicht Ihre eigenen deklarieren:

public delegate int MyDelegateType(string value)

und lassen Sie den Parameter stattdessen vom Typ sein MyDelegateType.

Jon Skeet
quelle
59
+1 Dies ist wirklich eine erstaunliche Antwort, um in zwei Minuten zu rasseln.
David Hall
3
Während Sie die Funktion mithilfe von Delegaten übergeben können, besteht ein traditionellerer OO-Ansatz darin, das Strategiemuster zu verwenden.
Paolo
20
@Paolo: Delegierte sind nur eine sehr bequeme Implementierung des Strategiemusters, bei der die betreffende Strategie nur eine einzige Methode erfordert. Es ist nicht so, dass dies gegen das Strategiemuster verstößt - aber es ist viel praktischer, als das Muster über Schnittstellen zu implementieren.
Jon Skeet
5
Sind die "klassischen" Delegaten (wie aus .NET 1/2 bekannt) noch nützlich oder sind sie aufgrund von Func / Action völlig veraltet? Fehlt in Ihrem Beispiel nicht ein Delegate-Schlüsselwort public **delegate** int MyDelegateType(string value)?
M4N
1
@ Martin: Doh! Danke für die Fehlerbehebung. Bearbeitet. Was das Deklarieren Ihrer eigenen Delegaten betrifft, kann es nützlich sein, dem Typnamen eine Bedeutung zu geben, aber ich habe seit .NET 3.5 selten einen eigenen Delegatentyp erstellt.
Jon Skeet
112

Aus dem Beispiel von OP:

 public static int Method1(string mystring)
 {
      return 1;
 }

 public static int Method2(string mystring)
 {
     return 2;
 }

Sie können Action Delegate ausprobieren! Und rufen Sie dann Ihre Methode mit auf

 public bool RunTheMethod(Action myMethodName)
 {
      myMethodName();   // note: the return value got discarded
      return true;
 }

RunTheMethod(() => Method1("MyString1"));

Oder

public static object InvokeMethod(Delegate method, params object[] args)
{
     return method.DynamicInvoke(args);
}

Dann rufen Sie einfach method auf

Console.WriteLine(InvokeMethod(new Func<string,int>(Method1), "MyString1"));

Console.WriteLine(InvokeMethod(new Func<string, int>(Method2), "MyString2"));
Zain Ali
quelle
4
Vielen Dank, dies brachte mich dahin, wo ich hin wollte, da ich eine allgemeinere "RunTheMethod" -Methode wollte, die mehrere Parameter zulässt. Übrigens sollte Ihr erster InvokeMethodLambda-Anruf RunTheMethodstattdessen sein
John
1
Wie John hat mir das wirklich geholfen, eine generische Methode zu entwickeln. Vielen Dank!
ean5533
2
Du hast meinen Tag gemacht;) Wirklich einfach zu bedienen und viel flexibler als die ausgewählte Antwort IMO.
Sidewinder94
Gibt es eine Möglichkeit, RunTheMethod (() => Method1 ("MyString1")) zu erweitern? einen Rückgabewert abrufen? Idealerweise ein Generikum?
Jay
Wenn Sie Parameter übergeben möchten, beachten Sie dies: stackoverflow.com/a/5414539/2736039
Ultimo_m
31
public static T Runner<T>(Func<T> funcToRun)
{
    //Do stuff before running function as normal
    return funcToRun();
}

Verwendungszweck:

var ReturnValue = Runner(() => GetUser(99));
kravits88
quelle
6
Es ist sehr benutzerfreundlich. Auf diese Weise können ein oder mehrere Parameter verwendet werden. Ich denke, die aktuellste Antwort ist diese.
Bafsar
Ich möchte eine Sache zu dieser Implementierung hinzufügen. Wenn die Methode, die Sie übergeben möchten, den Rückgabetyp void hat, können Sie diese Lösung nicht verwenden.
Imants Volkovs
@ImantsVolkovs Ich glaube, Sie können dies möglicherweise ändern, um eine Aktion anstelle einer Funktion zu verwenden, und die Signatur in ungültig ändern. Nicht 100% sicher.
Shockwave
2
Gibt es eine Möglichkeit, die Parameter an die aufgerufene Funktion zu übergeben?
Jimmy
16

Um eine möglichst vollständige Lösung zu teilen, werde ich am Ende drei verschiedene Vorgehensweisen vorstellen, aber jetzt gehe ich vom grundlegendsten Prinzip aus.


Eine kurze Einführung

Alle Sprachen, die auf CLR ( Common Language Runtime ) ausgeführt werden, wie C #, F # und Visual Basic, arbeiten unter einer VM, die den Code auf einer höheren Ebene ausführt als Muttersprachen wie C und C ++ (die direkt auf dem Computer kompiliert werden) Code). Daraus folgt, dass Methoden keine Art von kompiliertem Block sind, sondern nur strukturierte Elemente, die CLR erkennt. Sie können also nicht daran denken, eine Methode als Parameter zu übergeben, da Methoden selbst keine Werte erzeugen, da sie keine Ausdrücke sind! Es handelt sich vielmehr um Anweisungen, die im generierten CIL-Code definiert sind. Sie werden sich also dem Delegiertenkonzept stellen.


Was ist ein Delegierter?

Ein Delegat repräsentiert einen Zeiger auf eine Methode. Wie ich oben sagte, ist eine Methode kein Wert, daher gibt es eine spezielle Klasse in CLR-Sprachen Delegate, die jede Methode zusammenfasst.

Schauen Sie sich das folgende Beispiel an:

static void MyMethod()
{
    Console.WriteLine("I was called by the Delegate special class!");
}

static void CallAnyMethod(Delegate yourMethod)
{
    yourMethod.DynamicInvoke(new object[] { /*Array of arguments to pass*/ });
}

static void Main()
{
    CallAnyMethod(MyMethod);
}

Drei verschiedene Wege, das gleiche Konzept dahinter:

  • Weg 1
    Verwenden Sie die Delegatespezielle Klasse direkt wie im obigen Beispiel. Das Problem dieser Lösung besteht darin, dass Ihr Code deaktiviert wird, wenn Sie Ihre Argumente dynamisch übergeben, ohne sie auf die Typen in der Methodendefinition zu beschränken.

  • Weg 2
    Neben der Delegatespeziellen Klasse erstreckt sich das Delegatenkonzept auf benutzerdefinierte Delegaten, bei denen es sich um Definitionen von Methoden handelt, denen das delegateSchlüsselwort vorangestellt ist , und die sich wie normale Methoden verhalten. Sie werden dabei überprüft, sodass Sie einen einwandfrei sicheren Code erhalten.

    Hier ist ein Beispiel:

    delegate void PrintDelegate(string prompt);
    
    static void PrintSomewhere(PrintDelegate print, string prompt)
    {
        print(prompt);
    }
    
    static void PrintOnConsole(string prompt)
    {
        Console.WriteLine(prompt);
    }
    
    static void PrintOnScreen(string prompt)
    {
        MessageBox.Show(prompt);
    }
    
    static void Main()
    {
        PrintSomewhere(PrintOnConsole, "Press a key to get a message");
        Console.Read();
        PrintSomewhere(PrintOnScreen, "Hello world");
    }
  • Weg 3
    Alternativ können Sie einen Delegaten verwenden, der bereits im .NET-Standard definiert wurde:

    • Actionschließt ein voidohne Argumente ab.
    • Action<T1>schließt ein voidmit einem Argument ab.
    • Action<T1, T2>schließt a voidmit zwei Argumenten ab.
    • Und so weiter...
    • Func<TR>schließt eine Funktion mit dem TRRückgabetyp und ohne Argumente ab.
    • Func<T1, TR>schließt eine Funktion mit dem TRRückgabetyp und einem Argument ab.
    • Func<T1, T2, TR>schließt eine Funktion mit dem TRRückgabetyp und mit zwei Argumenten ab.
    • Und so weiter...

(Die letztere Lösung ist diejenige, die die meisten Leute gepostet haben.)

Davide Cannizzo
quelle
1
Sollte der Rückgabetyp eines Func <T> nicht der letzte sein? Func<T1,T2,TR>
Sanmcp
13

Sie sollten einen Func<string, int>Delegaten verwenden, der eine Funktion darstellt, die ein stringArgument als Argument verwendet und Folgendes zurückgibt int:

public bool RunTheMethod(Func<string, int> myMethod) {
    // do stuff
    myMethod.Invoke("My String");
    // do stuff
    return true;
}

Dann benutze es:

public bool Test() {
    return RunTheMethod(Method1);
}
Bruno Reis
quelle
3
Dies wird nicht kompiliert. Die TestMethode sollte seinreturn RunTheMethod(Method1);
pswg
7

Wenn Sie ändern möchten, welche Methode zur Laufzeit aufgerufen wird, würde ich die Verwendung eines Delegaten empfehlen: http://www.codeproject.com/KB/cs/delegates_step1.aspx

Sie können damit ein Objekt zum Speichern der aufzurufenden Methode erstellen und dieses bei Bedarf an Ihre anderen Methoden übergeben.

MadcapLaugher
quelle
2

Während die akzeptierte Antwort absolut korrekt ist, möchte ich eine zusätzliche Methode bereitstellen.

Ich bin hier gelandet, nachdem ich selbst nach einer Lösung für eine ähnliche Frage gesucht hatte. Ich erstelle ein Plugin-gesteuertes Framework und wollte als Teil davon, dass Benutzer Menüelemente zum Anwendungsmenü zu einer generischen Liste hinzufügen können, ohne ein tatsächliches MenuObjekt verfügbar zu machen, da das Framework möglicherweise auf anderen Plattformen bereitgestellt wird, die dies nicht habenMenu Benutzer Benutzeroberfläche bereitgestellt wird Objekte. Das Hinzufügen allgemeiner Informationen zum Menü ist einfach genug, aber es erwies sich als schmerzhaft, dem Plugin-Entwickler genügend Freiheit zu geben, um den Rückruf zu erstellen, wenn auf das Menü geklickt wird. Bis mir klar wurde, dass ich versuchte, das Rad neu zu erfinden und normale Menüs aufzurufen und den Rückruf von Ereignissen auszulösen!

Die Lösung, so einfach sie auch klingt, wenn Sie sie erst einmal erkannt haben, ist mir bisher entgangen.

Erstellen Sie einfach separate Klassen für jede Ihrer aktuellen Methoden, die bei Bedarf von einer Basis geerbt werden, und fügen Sie jeder Klasse einen Ereignishandler hinzu.

Wackelt
quelle
1

Hier ist ein Beispiel, das Ihnen helfen kann, besser zu verstehen, wie eine Funktion als Parameter übergeben wird.

Angenommen, Sie haben eine übergeordnete Seite und möchten ein untergeordnetes Popup-Fenster öffnen. Auf der übergeordneten Seite befindet sich ein Textfeld, das basierend auf dem untergeordneten Popup-Textfeld ausgefüllt werden sollte.

Hier müssen Sie einen Delegaten erstellen.

Parent.cs // Deklaration der Delegaten public delegate void FillName (String FirstName);

Erstellen Sie nun eine Funktion, die Ihr Textfeld ausfüllt, und die Funktion sollte Delegaten zuordnen

//parameters
public void Getname(String ThisName)
{
     txtname.Text=ThisName;
}

Wenn Sie auf die Schaltfläche klicken, müssen Sie ein untergeordnetes Popup-Fenster öffnen.

  private void button1_Click(object sender, RoutedEventArgs e)
  {
        ChildPopUp p = new ChildPopUp (Getname) //pass function name in its constructor

         p.Show();

    }

Im ChildPopUp-Konstruktor müssen Sie einen Parameter vom Typ 'Delegate' der übergeordneten // Seite erstellen

ChildPopUp.cs

    public  Parent.FillName obj;
    public PopUp(Parent.FillName objTMP)//parameter as deligate type
    {
        obj = objTMP;
        InitializeComponent();
    }



   private void OKButton_Click(object sender, RoutedEventArgs e)
    {


        obj(txtFirstName.Text); 
        // Getname() function will call automatically here
        this.DialogResult = true;
    }
Shrikant-Divyanet-Lösung
quelle
Bearbeitet, aber die Qualität dieser Antwort könnte noch verbessert werden.
SteakOverflow
1

Wenn Sie die Methode als Parameter übergeben möchten, verwenden Sie:

using System;

public void Method1()
{
    CallingMethod(CalledMethod);
}

public void CallingMethod(Action method)
{
    method();   // This will call the method that has been passed as parameter
}

public void CalledMethod()
{
    Console.WriteLine("This method is called by passing parameter");
}
Juned Khan Momin
quelle
0

Hier ist ein Beispiel ohne Parameter: http://en.csharp-online.net/CSharp_FAQ:_How_call_a_method_using_a_name_string

mit Parametern: http://www.daniweb.com/forums/thread98148.html#

Grundsätzlich übergeben Sie ein Array von Objekten zusammen mit dem Namen der Methode. Sie verwenden dann beide mit der Invoke-Methode.

params Object [] -Parameter

Jeremy Samuel
quelle
Beachten Sie, dass der Name der Methode nicht in einer Zeichenfolge enthalten ist, sondern als Methodengruppe. Delegierte sind hier die beste Antwort, keine Reflexion.
Jon Skeet
@Lette: Beim Methodenaufruf ist der als Argument verwendete Ausdruck eine Methodengruppe . Dies ist der Name einer Methode, die zur Kompilierungszeit bekannt ist, und der Compiler kann diese in einen Delegaten konvertieren. Dies unterscheidet sich stark von der Situation, in der der Name nur zur Ausführungszeit bekannt ist.
Jon Skeet
0
class PersonDB
{
  string[] list = { "John", "Sam", "Dave" };
  public void Process(ProcessPersonDelegate f)
  {
    foreach(string s in list) f(s);
  }
}

Die zweite Klasse ist Client, der die Speicherklasse verwendet. Es verfügt über eine Main-Methode, die eine Instanz von PersonDB erstellt, und ruft die Process-Methode dieses Objekts mit einer Methode auf, die in der Client-Klasse definiert ist.

class Client
{
  static void Main()
  {
    PersonDB p = new PersonDB();
    p.Process(PrintName);
  }
  static void PrintName(string name)
  {
    System.Console.WriteLine(name);
  }
}
x-rw
quelle