Passa metodo come parametro usando C #


694

Ho diversi metodi tutti con la stessa firma (parametri e valori restituiti) ma nomi diversi e interni dei metodi sono diversi. Voglio passare il nome del metodo per eseguire un altro metodo che invocherà il metodo passato.

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);
}

Questo codice non funziona ma è quello che sto cercando di fare. Quello che non capisco è come scrivere il codice RunTheMethod poiché ho bisogno di definire il parametro.


12
Perché non si passa un delegato invece del nome del metodo?
Mark Byers,

Risposte:


852

È possibile utilizzare il delegato Func in .net 3.5 come parametro nel metodo RunTheMethod. Il delegato di Func consente di specificare un metodo che accetta un numero di parametri di un tipo specifico e restituisce un singolo argomento di un tipo specifico. Ecco un esempio che dovrebbe funzionare:

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);
    }
}

51
Come cambierebbe la chiamata Func se il metodo ha come firma di ritorno vuoto e nessun parametro? Non riesco a far funzionare la sintassi.
user31673,

210
@unknown: in tal caso sarebbe Actioninvece di Func<string, int>.
Jon Skeet,

12
ma ora cosa succede se si desidera passare argomenti al metodo ??
john ktejik,

40
@ user396483 Ad esempio, Action<int,string>corrisponde a un metodo che accetta 2 parametri (int e stringa) e restituisce un vuoto.
serdar,

24
@NoelWidmer L'utilizzo Func<double,string,int>corrisponde a un metodo che accetta 2 parametri ( doublee string) e restituisce int. L'ultimo tipo specificato è il tipo restituito. È possibile utilizzare questo delegato per un massimo di 16 parametri. Se in qualche modo hai bisogno di più, scrivi il tuo delegato come public delegate TResult Func<in T1, in T2, (as many arguments as you want), in Tn, out TResult>(T1 arg1, T2 arg2, ..., Tn argn);. Per favore, correggimi se ho frainteso.
serdar,

356

Devi usare un delegato . In questo caso tutti i tuoi metodi accettano un stringparametro e restituiscono un int- questo è semplicemente rappresentato dal Func<string, int>delegato 1 . Quindi il tuo codice può diventare corretto con una semplice modifica come questa:

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

I delegati hanno molto più potere di questo, è vero. Ad esempio, con C # puoi creare un delegato da un'espressione lambda , in modo da poter invocare il tuo metodo in questo modo:

RunTheMethod(x => x.Length);

Ciò creerà una funzione anonima come questa:

// 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;
}

e quindi passare quel delegato al RunTheMethod metodo.

È possibile utilizzare i delegati per gli abbonamenti agli eventi, l'esecuzione asincrona, i callback - ogni genere di cose. Vale la pena leggerli, in particolare se si desidera utilizzare LINQ. Ho un articolo che tratta principalmente delle differenze tra delegati ed eventi, ma potresti trovarlo utile comunque.


1 Si basa solo sul Func<T, TResult>tipo di delegato generico nel framework; potresti facilmente dichiarare il tuo:

public delegate int MyDelegateType(string value)

e quindi rendere invece il parametro di tipo MyDelegateType.


59
+1 Questa è davvero una risposta incredibile per snocciolare in due minuti.
David Hall,

3
Mentre è possibile passare la funzione utilizzando i delegati, un approccio OO più tradizionale sarebbe quello di utilizzare il modello di strategia.
Paolo,

20
@Paolo: i delegati sono solo un'implementazione molto conveniente del modello di strategia in cui la strategia in questione richiede un solo metodo. Non è che questo vada contro il modello di strategia, ma è molto più conveniente che implementare il modello usando le interfacce.
Jon Skeet,

5
I delegati "classici" (come noto da .NET 1/2) sono ancora utili o sono completamente obsoleti a causa di Func / Action? Inoltre, nel tuo esempio non manca una parola chiave delegata public **delegate** int MyDelegateType(string value)?
M4N,

1
@Martin: Doh! Grazie per la correzione. Modificato. Per quanto riguarda la dichiarazione dei propri delegati, può essere utile dare un significato al nome del tipo, ma raramente ho creato il mio tipo di delegato da .NET 3.5.
Jon Skeet,

112

Dall'esempio di OP:

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

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

Puoi provare Action Delegate! E poi chiama il tuo metodo usando

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

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

O

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

Quindi chiama semplicemente il metodo

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

Console.WriteLine(InvokeMethod(new Func<string, int>(Method2), "MyString2"));

4
Grazie, questo mi ha portato dove volevo andare poiché volevo un metodo "RunTheMethod" più generico che consentisse più parametri. Tuttavia, la tua prima InvokeMethodchiamata lambda dovrebbe essere RunTheMethodinvece
John

1
Come John, questo mi ha davvero aiutato ad avere un metodo generico move. Grazie!
ean5533

2
Hai reso la mia giornata;) Davvero semplice da usare e molto più flessibile della risposta selezionata IMO.
Sidewinder94

C'è un modo per espandere su RunTheMethod (() => Method1 ("MyString1")); recuperare un valore di ritorno? Idealmente un generico?
Jay,

se si vuole passare i parametri essere consapevoli di questo: 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();
}

Uso:

var ReturnValue = Runner(() => GetUser(99));

6
È molto utile. In questo modo, è possibile utilizzare uno o più parametri. Immagino che la risposta più aggiornata sia questa.
bafsar,

Vorrei aggiungere una cosa su questa implementazione. Se il metodo che stai per passare ha un tipo restituito di vuoto, non puoi utilizzare questa soluzione.
Imants Volkovs,

@ImantsVolkovs Credo che potresti essere in grado di modificarlo per usare un'azione invece di un Func e cambiare la firma in nulla. Non sicuro al 100% però.
Shockwave,

2
C'è un modo per far passare i parametri alla funzione chiamata?
Jimmy,

16

Per condividere una soluzione il più completa possibile, finirò per presentare tre diversi modi di fare, ma ora inizierò dal principio più elementare.


Una breve introduzione

Tutti i linguaggi eseguiti su CLR ( Common Language Runtime ), come C #, F # e Visual Basic, funzionano in una macchina virtuale, che esegue il codice a un livello superiore rispetto alle lingue native come C e C ++ (che vengono compilate direttamente sulla macchina codice). Ne consegue che i metodi non sono alcun tipo di blocco compilato, ma sono solo elementi strutturati che CLR riconosce. Pertanto, non puoi pensare di passare un metodo come parametro, poiché i metodi non producono alcun valore se stessi, poiché non sono espressioni! Piuttosto, sono dichiarazioni, che sono definite nel codice CIL generato. Quindi, affronterai il concetto di delegato.


Che cos'è un delegato?

Un delegato rappresenta un puntatore a un metodo. Come ho detto sopra, un metodo non è un valore, quindi esiste una classe speciale nei linguaggi CLR,Delegate , che avvolge qualsiasi metodo.

Guarda il seguente esempio:

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);
}

Tre modi diversi, lo stesso concetto alla base:

  • Modo 1
    Utilizzare la Delegateclasse speciale direttamente come nell'esempio sopra. Il problema di questa soluzione è che il tuo codice verrà deselezionato mentre passi dinamicamente i tuoi argomenti senza limitarli ai tipi di quelli nella definizione del metodo.

  • Modo 2
    Oltre alla Delegateclasse speciale, il concetto di delegato si diffonde ai delegati personalizzati, che sono definizioni di metodi precedute dadelegate parola chiave e si comportano allo stesso modo dei metodi normali. Vengono quindi controllati, quindi otterrai un codice perfettamente sicuro.

    Ecco un esempio:

    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");
    }
  • Modo 3
    In alternativa, è possibile utilizzare un delegato che è già stato definito in .NET Standard:

    • Actiontermina un voidsenza argomenti.
    • Action<T1>termina un a voidcon un argomento.
    • Action<T1, T2>termina un a voidcon due argomenti.
    • E così via...
    • Func<TR>racchiude una funzione con TRtipo restituito e senza argomenti.
    • Func<T1, TR>racchiude una funzione con TRtipo restituito e con un argomento.
    • Func<T1, T2, TR> termina una funzione con TR tipo restituito e con due argomenti.
    • E così via...

(Quest'ultima soluzione è quella pubblicata dalla maggior parte delle persone.)


1
Il tipo di ritorno di un Func <T> non dovrebbe essere l'ultimo? Func<T1,T2,TR>
sanmcp,

13

È necessario utilizzare un Func<string, int>delegato, che rappresenta una funzione che accetta un stringargomento as e restituisce un int:

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

Quindi usalo:

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

3
Questo non verrà compilato. Il Testmetodo dovrebbe esserereturn RunTheMethod(Method1);
pswg


2

Mentre la risposta accettata è assolutamente corretta, vorrei fornire un metodo aggiuntivo.

Sono finito qui dopo aver fatto la mia ricerca di una soluzione a una domanda simile. Sto costruendo un framework guidato da plug-in e come parte di esso volevo che le persone potessero aggiungere voci di menu al menu applicazioni a un elenco generico senza esporre un Menuoggetto reale perché il framework potrebbe essere distribuito su altre piattaforme che non hannoMenu UI oggetti. L'aggiunta di informazioni generali sul menu è abbastanza semplice, ma consentire allo sviluppatore del plug-in abbastanza libertà di creare il callback per quando si fa clic sul menu si sta dimostrando una seccatura. Fino a quando mi sono reso conto che stavo cercando di reinventare la ruota e i normali menu chiamano e attivano la richiamata dagli eventi!

Quindi la soluzione, semplice come sembra una volta che te ne rendi conto, mi ha eluso fino ad ora.

Basta creare classi separate per ciascuno dei metodi correnti, ereditati da una base se necessario, e aggiungere un gestore di eventi a ciascuno di essi.


1

Ecco un esempio che può aiutarti a capire meglio come passare una funzione come parametro.

Supponiamo di avere una pagina padre e di voler aprire una finestra popup figlio. Nella pagina principale è presente una casella di testo che deve essere riempita in base alla casella di testo popup figlio.

Qui devi creare un delegato.

Parent.cs // dichiarazione dei delegati delegato pubblico void FillName (String FirstName);

Ora crea una funzione che riempirà la casella di testo e la funzione dovrebbe mappare i delegati

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

Ora facendo clic sul pulsante è necessario aprire una finestra popup Child.

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

         p.Show();

    }

Nel costruttore ChildPopUp è necessario creare il parametro di 'tipo delegato' della pagina // padre

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;
    }

Modificato ma la qualità di questa risposta potrebbe ancora essere migliorata.
SteakOverflow,

1

Se si desidera passare Metodo come parametro, utilizzare:

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");
}

0

Ecco un esempio senza un parametro: http://en.csharp-online.net/CSharp_FAQ:_How_call_a_method_using_a_name_string

con parametri: http://www.daniweb.com/forums/thread98148.html#

fondamentalmente passi una matrice di oggetti insieme al nome del metodo. quindi si utilizzano entrambi con il metodo Invoke.

params Parametri oggetto []


Nota che il nome del metodo non è in una stringa, ma in realtà è un gruppo di metodi. I delegati sono la migliore risposta qui, non la riflessione.
Jon Skeet,

@Lette: nell'invocazione del metodo, l'espressione utilizzata come argomento è un gruppo di metodi ; è il nome di un metodo che è noto in fase di compilazione e il compilatore può convertirlo in un delegato. Questo è molto diverso dalla situazione in cui il nome è noto solo al momento dell'esecuzione.
Jon Skeet,

0
class PersonDB
{
  string[] list = { "John", "Sam", "Dave" };
  public void Process(ProcessPersonDelegate f)
  {
    foreach(string s in list) f(s);
  }
}

La seconda classe è Client, che utilizzerà la classe di archiviazione. Ha un metodo Main che crea un'istanza di PersonDB e chiama il metodo Process di quell'oggetto con un metodo definito nella classe Client.

class Client
{
  static void Main()
  {
    PersonDB p = new PersonDB();
    p.Process(PrintName);
  }
  static void PrintName(string name)
  {
    System.Console.WriteLine(name);
  }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.