Przekaż metodę jako parametr za pomocą C #


694

Mam kilka metod o tej samej sygnaturze (parametry i zwracane wartości), ale różne nazwy i elementy wewnętrzne metod są różne. Chcę przekazać nazwę metody do uruchomienia do innej metody, która wywoła przekazaną metodę.

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

Ten kod nie działa, ale staram się to zrobić. Nie rozumiem, jak napisać kod RunTheMethod, ponieważ muszę zdefiniować parametr.


12
Dlaczego nie podajesz delegata zamiast nazwy metody?
Mark Byers

Odpowiedzi:


852

Możesz użyć delegata Func w .net 3.5 jako parametru w metodzie RunTheMethod. Delegat Func pozwala określić metodę, która pobiera szereg parametrów określonego typu i zwraca pojedynczy argument określonego typu. Oto przykład, który powinien działać:

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
Jak zmieni się wywołanie Func, jeśli metoda ma jako sygnaturę nieważność i brak parametrów? Nie mogę sprawić, by składnia zadziałała.
user31673,

211
@unknown: W takim przypadku byłoby to Actionzamiast Func<string, int>.
Jon Skeet,

12
ale co teraz, jeśli chcesz przekazać argumenty do metody?
john ktejik

40
@ user396483 Na przykład Action<int,string>odpowiada metodzie, która przyjmuje 2 parametry (int i ciąg) i zwraca wartość void.
serdar

24
@NoelWidmer Korzystanie Func<double,string,int>odpowiada metodzie, która przyjmuje 2 parametry ( doublei string) i zwraca int. Ostatni określony typ to typ zwracany. Możesz użyć tego delegata dla maksymalnie 16 parametrów. Jeśli potrzebujesz czegoś więcej, napisz swojego delegata jako public delegate TResult Func<in T1, in T2, (as many arguments as you want), in Tn, out TResult>(T1 arg1, T2 arg2, ..., Tn argn);. Proszę mnie poprawić, jeśli źle zrozumiałem.
serdar

356

Musisz użyć delegata . W takim przypadku wszystkie metody pobierają stringparametr i zwracają an int- jest to najprościej reprezentowane przez Func<string, int>delegata 1 . Aby Twój kod mógł być poprawny, wystarczy tak prosta zmiana:

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

Trzeba przyznać, że delegaci mają o wiele więcej władzy. Na przykład za pomocą C # możesz utworzyć delegata z wyrażenia lambda , abyś mógł wywołać swoją metodę w ten sposób:

RunTheMethod(x => x.Length);

Spowoduje to utworzenie anonimowej funkcji:

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

a następnie przekaż tego delegata do RunTheMethodmetody.

Możesz używać delegatów do subskrypcji zdarzeń, wykonywania asynchronicznego, wywołań zwrotnych - wszelkiego rodzaju rzeczy. Warto o nich poczytać, szczególnie jeśli chcesz korzystać z LINQ. Mam artykuł, który dotyczy głównie różnic między delegatami i wydarzeniami, ale i tak może ci się przydać.


1 Jest to oparte na ogólnym Func<T, TResult>typie delegata w ramach; możesz łatwo zadeklarować własne:

public delegate int MyDelegateType(string value)

a następnie ustaw MyDelegateTypezamiast tego parametr type .


59
+1 To naprawdę niesamowita odpowiedź na grzechotkę za dwie minuty.
David Hall

3
Chociaż możesz przekazać tę funkcję za pomocą delegatów, bardziej tradycyjnym podejściem OO byłoby użycie wzorca strategii.
Paolo,

21
@Paolo: Delegaci są po prostu bardzo wygodną implementacją wzorca strategii, gdzie strategia wymaga tylko jednej metody. Nie jest to sprzeczne ze schematem strategii - ale jest to o wiele wygodniejsze niż implementacja schematu za pomocą interfejsów.
Jon Skeet

5
Czy „klasyczni” delegaci (znani z .NET 1/2) są nadal przydatni, czy też są całkowicie przestarzali z powodu Func / Action? Ponadto, czy w Twoim przykładzie nie brakuje delegowanego słowa kluczowego public **delegate** int MyDelegateType(string value)?
M4N

1
@Martin: Doh! Dzięki za poprawkę. Edytowane. Jeśli chodzi o deklarowanie własnych delegatów - przydatne może być nadanie nazwy typu znaczenia, ale rzadko tworzyłem własny typ delegata od .NET 3.5.
Jon Skeet

112

Z przykładu OP:

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

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

Możesz spróbować Action Delegate! A następnie wywołaj metodę za pomocą

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

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

Lub

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

Następnie po prostu wywołaj metodę

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

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

4
Dzięki, znalazłem się tam, gdzie chciałem iść, ponieważ chciałem bardziej ogólnej metody „RunTheMethod”, która pozwoliłaby na wiele parametrów. Btw Twoja pierwsza InvokeMethodrozmowa lambda powinna być RunTheMethodzamiast
John

1
Podobnie jak John, naprawdę pomogło mi to zastosować ogólną metodę przenoszenia. Dzięki!
ean5533

2
Sprawiłeś, że mój dzień;) Naprawdę prosty w użyciu i znacznie bardziej elastyczny niż wybrana odpowiedź IMO.
Sidewinder94

Czy istnieje sposób na rozszerzenie w RunTheMethod (() => Method1 („MyString1”)); pobrać wartość zwracaną? Idealnie ogólny?
Jay

jeśli chcesz przekazać parametry, pamiętaj o tym: 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();
}

Stosowanie:

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

6
To bardzo użyteczne. W ten sposób można użyć jednego lub wielu parametrów. Chyba najbardziej aktualna odpowiedź to:
bafsar

Chciałbym dodać jedną rzecz o tej implementacji. Jeśli metoda, którą zamierzasz przekazać, ma typ zwrotu void, nie możesz użyć tego rozwiązania.
Imants Volkovs

@ImantsVolkovs Wierzę, że możesz to zmodyfikować, aby użyć akcji zamiast func i zmienić podpis na unieważniony. Nie jestem jednak w 100% pewien.
Shockwave

2
Czy jest jakiś sposób na przekazanie parametrów do wywoływanej funkcji?
Jimmy

17

Aby podzielić się możliwie jak najbardziej kompletnym rozwiązaniem, przedstawię trzy różne sposoby działania, ale teraz zacznę od najbardziej podstawowej zasady.


Krótkie wprowadzenie

Wszystkie języki działające na CLR ( Common Language Runtime ), takie jak C #, F # i Visual Basic, działają pod maszyną wirtualną, która uruchamia kod na wyższym poziomie niż języki rodzime, takie jak C i C ++ (które kompilują się bezpośrednio na maszynie kod). Wynika z tego, że metody nie są żadnym skompilowanym blokiem, ale są tylko elementami strukturalnymi, które rozpoznaje CLR. Dlatego nie możesz pomyśleć o przekazaniu metody jako parametru, ponieważ metody same nie generują żadnych wartości, ponieważ nie są wyrażeniami! Są to raczej instrukcje zdefiniowane w wygenerowanym kodzie CIL. Więc napotkasz koncepcję delegata.


Kim jest delegat?

Delegat reprezentuje wskaźnik do metody. Jak powiedziałem powyżej, metoda nie jest wartością, dlatego istnieje specjalna klasa w językach CLR Delegate, która zawiera dowolną metodę.

Spójrz na następujący przykład:

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

Trzy różne sposoby, ta sama koncepcja:

  • Sposób 1
    Użyj Delegatespecjalnej klasy bezpośrednio jak w powyższym przykładzie. Problemem tego rozwiązania jest to, że Twój kod nie będzie zaznaczony podczas dynamicznego przekazywania argumentów bez ograniczania ich do typów z definicji metody.

  • Sposób 2
    Oprócz Delegateklasy specjalnej, koncepcja delegata rozprzestrzenia się na niestandardowych delegatów, które są definicjami metod poprzedzonych przezdelegate słowem kluczowym i zachowują się tak samo jak normalne metody. Są one w ten sposób sprawdzane, więc opracujesz bezbłędnie bezpieczny kod.

    Oto przykład:

    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");
    }
  • Sposób 3
    Alternatywnie możesz użyć delegata, który został już zdefiniowany w .NET Standard:

    • Actionpodsumowuje voidbez argumentów.
    • Action<T1>podsumowuje voidjednym argumentem.
    • Action<T1, T2>podsumowuje voidz dwoma argumentami.
    • I tak dalej...
    • Func<TR>podsumowuje funkcję TRtypu zwracanego i bez argumentów.
    • Func<T1, TR>podsumowuje funkcję z TRtypem zwracanym i jednym argumentem.
    • Func<T1, T2, TR> kończy funkcję za pomocą TR typem zwracanym i dwoma argumentami.
    • I tak dalej...

(To drugie rozwiązanie jest tym, które opublikowało najwięcej osób).


1
Czy typ zwracany przez Func <T> nie powinien być ostatni? Func<T1,T2,TR>
sanmcp

13

Należy użyć Func<string, int>delegata, który reprezentuje funkcję przyjmującą stringjako argument i zwracającą int:

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

Następnie użyj go:

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

3
To się nie skompiluje. TestMetoda powinna byćreturn RunTheMethod(Method1);
pswg


2

Chociaż zaakceptowana odpowiedź jest absolutnie poprawna, chciałbym podać dodatkową metodę.

Skończyło się na tym, że szukałem rozwiązania podobnego pytania. Buduję strukturę opartą na wtyczkach i jako część tego chciałem, aby ludzie mogli dodawać elementy menu do menu aplikacji do ogólnej listy bez ujawniania rzeczywistego Menuobiektu, ponieważ struktura może być wdrażana na innych platformach, które nie mająMenu interfejsu użytkownika przedmioty Dodawanie ogólnych informacji o menu jest dość łatwe, ale pozwolenie twórcy wtyczki na swobodę tworzenia oddzwaniania, gdy kliknięcie menu okazało się być uciążliwe. Dopóki nie przyszło mi do głowy, że próbuję na nowo wynaleźć koło i normalne wywołanie menu i wywołać oddzwanianie od zdarzeń!

Tak więc rozwiązanie, tak proste, jak się wydaje, kiedy się zorientujesz, umykało mi do tej pory.

Po prostu utwórz oddzielne klasy dla każdej z twoich obecnych metod, w razie potrzeby odziedziczone z bazy, i po prostu dodaj do każdej z nich procedurę obsługi zdarzeń.


1

Oto przykład, który pomoże ci lepiej zrozumieć, jak przekazać funkcję jako parametr.

Załóżmy, że masz stronę nadrzędną i chcesz otworzyć podrzędne okno podręczne. Na stronie nadrzędnej znajduje się pole tekstowe, które powinno zostać wypełnione na podstawie podrzędnego pola tekstowego wyskakującego.

Tutaj musisz utworzyć delegata.

Parent.cs // deklaracja delegatów public delegate void FillName (String FirstName);

Teraz utwórz funkcję, która wypełni pole tekstowe, a funkcja powinna mapować delegatów

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

Teraz po kliknięciu przycisku musisz otworzyć okno podręczne Dziecko.

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

         p.Show();

    }

W konstruktorze ChildPopUp musisz utworzyć parametr „typu delegata” nadrzędnej // strony

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

Edytowane, ale jakość tej odpowiedzi można jeszcze poprawić.
SteakOverflow

1

Jeśli chcesz przekazać metodę jako parametr, użyj:

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

Oto przykład bez parametru: http://en.csharp-online.net/CSharp_FAQ:_How_call_a_method_using_a_name_string

z parametrami: http://www.daniweb.com/forums/thread98148.html#

w zasadzie przekazujesz tablicę obiektów wraz z nazwą metody. następnie używasz obu metod Invoke.

params Parametry Object []


Zauważ, że nazwa metody nie jest ciągiem - to właściwie grupa metod. Delegaci są tutaj najlepszą odpowiedzią, a nie refleksją.
Jon Skeet,

@Lette: W wywołaniu metody wyrażenie użyte jako argument jest grupą metod ; jest to nazwa metody znanej w czasie kompilacji, a kompilator może przekonwertować ją na delegata. To bardzo różni się od sytuacji, w której nazwa jest znana tylko w czasie wykonywania.
Jon Skeet,

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

Druga klasa to Klient, który będzie korzystał z klasy pamięci. Ma metodę Main, która tworzy instancję PersonDB i wywołuje metodę Process tego obiektu za pomocą metody zdefiniowanej w klasie Client.

class Client
{
  static void Main()
  {
    PersonDB p = new PersonDB();
    p.Process(PrintName);
  }
  static void PrintName(string name)
  {
    System.Console.WriteLine(name);
  }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.