Lulus Metode sebagai Parameter menggunakan C #

694

Saya punya beberapa metode semua dengan tanda tangan yang sama (parameter dan nilai kembali) tetapi nama yang berbeda dan internal metode berbeda. Saya ingin meneruskan nama metode untuk menjalankan ke metode lain yang akan memanggil metode lewat.

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

Kode ini tidak berfungsi tetapi inilah yang saya coba lakukan. Yang tidak saya mengerti adalah bagaimana menulis kode RunTheMethod karena saya perlu mendefinisikan parameter.

pengguna31673
sumber
12
Mengapa Anda tidak lulus delegasi alih-alih nama metode?
Mark Byers

Jawaban:

852

Anda bisa menggunakan delegasi Func di .net 3.5 sebagai parameter dalam metode RunTheMethod Anda. Delegasi Func memungkinkan Anda untuk menentukan metode yang mengambil sejumlah parameter dari jenis tertentu dan mengembalikan argumen tunggal dari jenis tertentu. Ini adalah contoh yang bisa digunakan:

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
sumber
51
Bagaimana panggilan Func berubah jika Metode memiliki tanda tangan pengembalian tidak berlaku dan tidak ada parameter? Sepertinya saya tidak bisa mendapatkan sintaks untuk bekerja.
user31673
210
@unknown: Dalam hal ini akan menjadi Actionbukan Func<string, int>.
Jon Skeet
12
tetapi sekarang bagaimana jika Anda ingin menyampaikan argumen ke metode ??
john ktejik
40
@ user396483 Misalnya, Action<int,string>terkait dengan metode yang mengambil 2 parameter (int dan string) dan mengembalikan batal.
serdar
24
@NoelWidmer Menggunakan Func<double,string,int>terkait dengan metode yang mengambil 2 parameter ( doubledanstring ) dan kembali int. Jenis yang ditentukan terakhir adalah jenis kembali. Anda dapat menggunakan delegasi ini hingga 16 parameter. Jika Anda membutuhkan lebih banyak, tuliskan delegasi Anda sendiri sebagai public delegate TResult Func<in T1, in T2, (as many arguments as you want), in Tn, out TResult>(T1 arg1, T2 arg2, ..., Tn argn);. Harap perbaiki saya jika saya salah paham.
serdar
356

Anda perlu menggunakan delegasi . Dalam hal ini semua metode Anda mengambil stringparameter dan mengembalikan int- ini paling mudah diwakili oleh Func<string, int>delegasi 1 . Jadi kode Anda dapat menjadi benar dengan perubahan sesederhana ini:

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

Para delegasi memiliki kekuatan lebih besar dari ini, harus diakui. Misalnya, dengan C # Anda dapat membuat delegasi dari ekspresi lambda , sehingga Anda dapat memanggil metode Anda dengan cara ini:

RunTheMethod(x => x.Length);

Itu akan membuat fungsi anonim seperti ini:

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

dan kemudian meneruskan delegasi itu ke RunTheMethodmetode.

Anda dapat menggunakan delegasi untuk berlangganan acara, eksekusi asinkron, callback - semua jenis hal. Sebaiknya baca di atasnya, terutama jika Anda ingin menggunakan LINQ. Saya memiliki artikel yang sebagian besar tentang perbedaan antara delegasi dan acara, tetapi Anda mungkin tetap menemukan manfaatnya.


1 Ini hanya berdasarkan pada Func<T, TResult>jenis delegasi umum dalam kerangka kerja; Anda dapat dengan mudah mendeklarasikan sendiri:

public delegate int MyDelegateType(string value)

dan kemudian membuat parameter menjadi tipe MyDelegateTypegantinya.

Jon Skeet
sumber
59
+1 Ini benar-benar jawaban yang luar biasa untuk berbunyi dalam dua menit.
David Hall
3
Meskipun Anda dapat melewati fungsi menggunakan delegasi, pendekatan OO yang lebih tradisional akan menggunakan pola strategi.
Paolo
20
@ Paolo: Delegasi hanyalah implementasi yang sangat nyaman dari pola strategi di mana strategi tersebut hanya memerlukan satu metode. Ini tidak seperti ini bertentangan dengan pola strategi - tapi itu jauh lebih nyaman daripada menerapkan pola menggunakan antarmuka.
Jon Skeet
5
Apakah delegasi "klasik" (seperti diketahui dari .NET 1/2) masih berguna, atau apakah mereka benar-benar usang karena Fungsi / Tindakan? Juga, bukankah ada kata kunci delegasi yang hilang dalam contoh Anda public **delegate** int MyDelegateType(string value)?
M4N
1
@ Martin: Doh! Terima kasih untuk perbaikannya. Diedit. Adapun mendeklarasikan delegasi Anda sendiri - mungkin berguna untuk memberi nama jenis arti, tetapi saya jarang membuat tipe delegasi saya sendiri sejak .NET 3.5.
Jon Skeet
112

Dari contoh OP:

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

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

Anda dapat mencoba Aksi Delegasi! Dan kemudian panggil metode Anda menggunakan

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

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

Atau

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

Maka cukup panggil metode

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

Console.WriteLine(InvokeMethod(new Func<string, int>(Method2), "MyString2"));
Zain Ali
sumber
4
Terima kasih, ini membuat saya ke mana saya ingin pergi karena saya ingin metode "RunTheMethod" yang lebih umum yang akan memungkinkan untuk beberapa parameter. AlihInvokeMethodRunTheMethod
John
1
Seperti John, ini sangat membantu saya memiliki metode generik bergerak. Terima kasih!
ean5533
2
Anda membuat hari saya;) Sangat mudah digunakan dan jauh lebih fleksibel daripada jawaban IMO yang dipilih.
Sidewinder94
Apakah ada cara untuk memperluas di RunTheMethod (() => Method1 ("MyString1")); untuk mengambil nilai kembali? Idealnya generik?
Jay
jika Anda ingin memberikan parameter, ketahuilah ini: 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();
}

Pemakaian:

var ReturnValue = Runner(() => GetUser(99));
kravits88
sumber
6
Ini sangat bermanfaat. Dengan cara ini, bisa menggunakan satu atau banyak parameter. Saya kira, jawaban terbaru adalah ini.
bafsar
Saya ingin menambahkan satu hal tentang implementasi ini. Jika metode yang akan Anda lewati memiliki tipe batal, maka Anda tidak dapat menggunakan solusi ini.
Imants Volkovs
@ImantsVolkovs Saya yakin Anda mungkin dapat memodifikasi ini untuk menggunakan Action alih-alih Funcs, dan mengubah tanda tangan untuk membatalkan. Tidak 100% yakin.
Shockwave
2
Apakah ada cara untuk mendapatkan parameter yang diteruskan ke fungsi yang disebut?
Jimmy
16

Untuk berbagi solusi selengkap mungkin, saya akan berakhir dengan menghadirkan tiga cara berbeda untuk melakukan, tetapi sekarang saya akan mulai dari prinsip paling dasar.


Perkenalan singkat

Semua bahasa yang berjalan di atas CLR ( Common Language Runtime ), seperti C #, F #, dan Visual Basic, bekerja di bawah VM, yang menjalankan kode pada tingkat yang lebih tinggi daripada bahasa asli seperti C dan C ++ (yang langsung dikompilasi ke mesin kode). Oleh karena itu metode tidak berupa blok terkompilasi, tetapi mereka hanya elemen terstruktur yang diakui CLR. Dengan demikian, Anda tidak dapat berpikir untuk meneruskan metode sebagai parameter, karena metode tidak menghasilkan nilai sendiri, karena mereka bukan ekspresi! Sebaliknya, itu adalah pernyataan, yang didefinisikan dalam kode CIL yang dihasilkan. Jadi, Anda akan menghadapi konsep delegasi.


Apa itu delegasi?

Delegasi mewakili pointer ke metode. Seperti yang saya katakan di atas, metode bukan nilai, maka ada kelas khusus dalam bahasa CLR Delegate, yang merangkum metode apa pun.

Lihatlah contoh berikut:

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

Tiga cara berbeda, konsep yang sama di belakang:

  • Cara 1
    Gunakan Delegatekelas khusus secara langsung seperti contoh di atas. Masalah dari solusi ini adalah bahwa kode Anda akan tidak dicentang ketika Anda memberikan argumen Anda secara dinamis tanpa membatasi mereka ke jenis yang ada dalam definisi metode.

  • Cara 2
    Selain Delegatekelas khusus, konsep delegasi menyebar ke delegasi khusus, yang merupakan definisi metode yang didahului olehdelegate kata kunci, dan mereka berperilaku sama seperti metode normal. Dengan demikian mereka diperiksa, sehingga Anda akan menemukan kode aman tanpa cacat.

    Ini sebuah contoh:

    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");
    }
  • Cara 3
    Atau, Anda dapat menggunakan delegasi yang telah ditentukan dalam .NET Standard:

    • Actionmembungkus voidtanpa argumen.
    • Action<T1>mengakhiri voiddengan satu argumen.
    • Action<T1, T2>membungkus voiddengan dua argumen.
    • Dan seterusnya...
    • Func<TR>membungkus fungsi dengan TRtipe kembali dan tanpa argumen.
    • Func<T1, TR>merangkai fungsi dengan TRtipe kembali dan dengan satu argumen.
    • Func<T1, T2, TR> mengakhiri fungsi dengan TR tipe kembali dan dengan dua argumen.
    • Dan seterusnya...

(Solusi terakhir adalah yang paling banyak diposting orang.)

Davide Cannizzo
sumber
1
Bukankah seharusnya jenis fungsi yang dikembalikan <T> menjadi yang terakhir? Func<T1,T2,TR>
sanmcp
13

Anda harus menggunakan Func<string, int>delegasi, yang mewakili fungsi mengambil stringargumen sebagai dan mengembalikan int:

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

Kemudian gunakan:

public bool Test() {
    return RunTheMethod(Method1);
}
Bruno Reis
sumber
3
Ini tidak akan dikompilasi. The TestMetode harusreturn RunTheMethod(Method1);
pswg
7

Jika Anda ingin kemampuan untuk mengubah metode mana yang dipanggil saat dijalankan saya akan merekomendasikan menggunakan delegasi: http://www.codeproject.com/KB/cs/delegates_step1.aspx

Ini akan memungkinkan Anda untuk membuat objek untuk menyimpan metode untuk memanggil dan Anda bisa meneruskannya ke metode lain ketika diperlukan.

MadcapLaugher
sumber
2

Meskipun jawaban yang diterima benar-benar benar, saya ingin memberikan metode tambahan.

Saya berakhir di sini setelah melakukan pencarian sendiri untuk solusi untuk pertanyaan serupa. Saya sedang membangun kerangka kerja yang digerakkan oleh plugin, dan sebagai bagian dari itu saya ingin orang-orang dapat menambahkan item menu ke menu aplikasi ke daftar generik tanpa mengekspos Menuobjek yang sebenarnya karena kerangka kerja tersebut dapat digunakan pada platform lain yang tidak memiliki MenuUI benda. Menambahkan info umum tentang menu cukup mudah, tetapi memungkinkan pengembang plugin cukup bebas untuk membuat panggilan balik ketika menu diklik terbukti menyebalkan. Sampai saya sadar bahwa saya sedang mencoba untuk menemukan kembali roda dan panggilan menu normal dan memicu panggilan balik dari acara!

Jadi solusinya, sesederhana kedengarannya begitu Anda menyadarinya, menghindari saya sampai sekarang.

Cukup buat kelas terpisah untuk masing-masing metode Anda saat ini, diwarisi dari basis jika Anda harus, dan tambahkan saja pengendali acara ke masing-masing metode.

Goyangan
sumber
1

Berikut adalah contoh yang dapat membantu Anda lebih memahami cara melewatkan fungsi sebagai parameter.

Misalkan Anda memiliki halaman Induk dan Anda ingin membuka jendela sembulan anak. Di halaman induk ada kotak teks yang harus diisi berdasarkan kotak teks popup anak.

Di sini Anda perlu membuat delegasi.

Parent.cs // deklarasi delegasi delegasi publik batal FillName (String FirstName);

Sekarang buat fungsi yang akan mengisi kotak teks Anda dan fungsi harus memetakan delegasi

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

Sekarang pada tombol klik Anda perlu membuka jendela popup Anak.

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

         p.Show();

    }

DI konstruktor ChildPopUp Anda perlu membuat parameter 'tipe delegasi' dari halaman induk //

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;
    }
Solusi Shrikant-Divyanet
sumber
Diedit tetapi kualitas jawaban ini masih dapat ditingkatkan.
SteakOverflow
1

Jika Anda ingin meneruskan Metode sebagai parameter, gunakan:

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
sumber
0

Ini adalah contoh tanpa parameter: http://en.csharp-online.net/CSharp_FAQ:_How_call_a_method_using_a_name_string

dengan params: http://www.daniweb.com/forums/thread98148.html#

Anda pada dasarnya mengirimkan array objek beserta nama metode. Anda kemudian menggunakan keduanya dengan metode Invoke.

parameter params Object []

Jeremy Samuel
sumber
Perhatikan bahwa nama metode tidak dalam string - sebenarnya sebagai grup metode. Delegasi adalah jawaban terbaik di sini, bukan refleksi.
Jon Skeet
@Lette: Dalam doa metode, ekspresi yang digunakan sebagai argumen adalah grup metode ; itu nama metode yang dikenal pada waktu kompilasi, dan kompilator dapat mengubahnya menjadi delegasi. Ini sangat berbeda dengan situasi di mana nama hanya diketahui pada waktu eksekusi.
Jon Skeet
0
class PersonDB
{
  string[] list = { "John", "Sam", "Dave" };
  public void Process(ProcessPersonDelegate f)
  {
    foreach(string s in list) f(s);
  }
}

Kelas kedua adalah Klien, yang akan menggunakan kelas penyimpanan. Ini memiliki metode Utama yang membuat instance dari PersonDB, dan ia memanggil metode Proses objek itu dengan metode yang didefinisikan dalam kelas Klien.

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