Truyền phương thức làm tham số bằng C #


694

Tôi có một số phương thức tất cả có cùng chữ ký (tham số và giá trị trả về) nhưng tên khác nhau và nội hàm của phương thức là khác nhau. Tôi muốn truyền tên của phương thức để chạy sang phương thức khác sẽ gọi phương thức đã truyền.

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

Mã này không hoạt động nhưng đây là những gì tôi đang cố gắng làm. Điều tôi không hiểu là làm thế nào để viết mã RunTheMethod vì tôi cần xác định tham số.


12
Tại sao bạn không vượt qua một đại biểu thay vì tên của phương thức?
Mark Byers

Câu trả lời:


852

Bạn có thể sử dụng đại biểu Func trong .net 3.5 làm tham số trong phương thức RunTheMethod của mình. Đại biểu Func cho phép bạn chỉ định một phương thức lấy một số tham số của một loại cụ thể và trả về một đối số của một loại cụ thể. Đây là một ví dụ nên hoạt động:

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
Cuộc gọi Func sẽ thay đổi như thế nào nếu Phương thức có chữ ký trả về khoảng trống và không có tham số? Tôi dường như không thể có được cú pháp để làm việc.
dùng31673

210
@unknown: Trong trường hợp đó, nó sẽ Actionthay thế Func<string, int>.
Jon Skeet

12
Nhưng bây giờ nếu bạn muốn truyền tham số vào phương thức thì sao ??
john ktejik

40
@ user394483 Ví dụ, Action<int,string>tương ứng với một phương thức lấy 2 tham số (int và chuỗi) và trả về void.
serdar

24
@NoelWidmer Sử dụng Func<double,string,int>tương ứng với một phương thức có 2 tham số ( doublestring) và trả về int. Loại được chỉ định cuối cùng là loại trả về. Bạn có thể sử dụng đại biểu này cho tối đa 16 tham số. Nếu bạn bằng cách nào đó cần nhiều hơn, hãy viết đại biểu của riêng bạn như public delegate TResult Func<in T1, in T2, (as many arguments as you want), in Tn, out TResult>(T1 arg1, T2 arg2, ..., Tn argn);. Xin hãy sửa tôi nếu tôi hiểu lầm.
serdar

356

Bạn cần sử dụng một đại biểu . Trong trường hợp này, tất cả các phương thức của bạn đều lấy stringtham số và trả về một int- điều này được biểu diễn đơn giản nhất bởiFunc<string, int> đại biểu 1 . Vì vậy, mã của bạn có thể trở nên chính xác với một thay đổi đơn giản như sau:

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

Các đại biểu có nhiều quyền lực hơn thế này, phải thừa nhận. Ví dụ: với C #, bạn có thể tạo một đại biểu từ một biểu thức lambda , vì vậy bạn có thể gọi phương thức của mình theo cách này:

RunTheMethod(x => x.Length);

Điều đó sẽ tạo ra một chức năng ẩn danh như thế này:

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

và sau đó chuyển đại biểu đó cho RunTheMethodphương thức.

Bạn có thể sử dụng các đại biểu cho đăng ký sự kiện, thực thi không đồng bộ, gọi lại - tất cả các loại điều. Rất đáng để đọc về chúng, đặc biệt nếu bạn muốn sử dụng LINQ. Tôi có một bài viết đó là chủ yếu về sự khác biệt giữa các đại biểu và các sự kiện, nhưng bạn có thể tìm thấy nó anyway hữu ích.


1 Điều này chỉ dựa trên loại Func<T, TResult>đại biểu chung trong khung; bạn có thể dễ dàng tuyên bố của riêng bạn:

public delegate int MyDelegateType(string value)

và sau đó làm cho tham số là loại MyDelegateTypethay thế.


59
+1 Đây thực sự là một câu trả lời tuyệt vời để rầm rộ trong hai phút.
Hội trường David

3
Mặc dù bạn có thể vượt qua chức năng bằng cách sử dụng các đại biểu, một cách tiếp cận OO truyền thống hơn sẽ là sử dụng mẫu chiến lược.
Paolo

20
@Paolo: Các đại biểu chỉ là một triển khai rất thuận tiện của mẫu chiến lược trong đó chiến lược được đề cập chỉ yêu cầu một phương pháp duy nhất. Nó không giống như điều này đi ngược lại với mẫu chiến lược - nhưng nó tiện lợi hơn rất nhiều so với việc triển khai mẫu bằng các giao diện.
Jon Skeet

5
Các đại biểu "cổ điển" (như được biết từ .NET 1/2) vẫn còn hữu ích hay chúng hoàn toàn lỗi thời vì Func / Action? Ngoài ra, không có từ khóa đại biểu bị thiếu trong ví dụ của bạn public **delegate** int MyDelegateType(string value)?
M4N

1
@Martin: Đừng! Cảm ơn đã sửa chữa. Đã chỉnh sửa. Đối với việc khai báo các đại biểu của riêng bạn - có thể hữu ích khi đặt tên loại một số ý nghĩa, nhưng tôi hiếm khi tạo ra loại đại biểu của riêng mình kể từ .NET 3.5.
Jon Skeet

112

Từ ví dụ của OP:

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

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

Bạn có thể thử Action Delegate! Và sau đó gọi phương thức của bạn bằng cách sử dụng

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

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

Hoặc là

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

Sau đó, chỉ cần gọi phương thức

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

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

4
Cảm ơn, điều này đã đưa tôi đến nơi tôi muốn đến vì tôi muốn một phương thức "RunTheMethod" chung chung hơn cho phép nhiều tham số. Btw InvokeMethodcuộc gọi lambda đầu tiên của bạn nên được RunTheMethodthay thế
John

1
Giống như John, điều này thực sự giúp tôi có một phương pháp chung chung. Cảm ơn!
ean5533

2
Bạn đã tạo ra ngày của tôi;) Thực sự đơn giản để sử dụng và linh hoạt hơn nhiều so với câu trả lời được chọn IMO.
Sidewinder94

Có cách nào để mở rộng trên RunTheMethod (() => Phương thức1 ("MyString1")); để lấy giá trị trả về? Lý tưởng là chung chung?
Jay

nếu bạn muốn truyền tham số, hãy lưu ý điều này: 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();
}

Sử dụng:

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

6
Nó rất hữu dụng. Với cách này, có thể sử dụng một hoặc nhiều tham số. Tôi đoán, câu trả lời cập nhật nhất là đây.
bafsar

Tôi muốn thêm một điều về việc thực hiện này. Nếu phương thức bạn sắp chuyển có loại khoảng trống, thì bạn không thể sử dụng giải pháp này.
Imants Volkovs

@ImantsVolkovs Tôi tin rằng bạn có thể sửa đổi điều này để sử dụng Hành động thay vì Func và thay đổi chữ ký thành vô hiệu. Không chắc chắn 100% mặc dù.
Shockwave

2
Có cách nào để có được các tham số được truyền cho hàm được gọi không?
Jimmy

16

Để chia sẻ một giải pháp hoàn chỉnh nhất có thể, tôi sẽ kết thúc bằng cách trình bày ba cách làm khác nhau, nhưng bây giờ tôi sẽ bắt đầu từ nguyên tắc cơ bản nhất.


Giới thiệu ngắn gọn

Tất cả các ngôn ngữ chạy trên CLR ( Thời gian chạy ngôn ngữ chung ), chẳng hạn như C #, F # và Visual Basic, hoạt động theo VM, chạy mã ở mức cao hơn các ngôn ngữ bản địa như C và C ++ (trực tiếp biên dịch cho máy mã). Nó tuân theo các phương thức không phải là bất kỳ loại khối được biên dịch nào, nhưng chúng chỉ là các phần tử có cấu trúc mà CLR nhận ra. Do đó, bạn không thể nghĩ truyền một phương thức làm tham số, bởi vì các phương thức không tự tạo ra bất kỳ giá trị nào, vì chúng không phải là biểu thức! Thay vào đó, chúng là các câu lệnh, được xác định trong mã CIL được tạo. Vì vậy, bạn sẽ phải đối mặt với khái niệm đại biểu.


Đại biểu là gì?

Một đại biểu đại diện cho một con trỏ đến một phương thức. Như tôi đã nói ở trên, một phương thức không phải là một giá trị, do đó có một lớp đặc biệt trong các ngôn ngữ CLR Delegate, bao bọc bất kỳ phương thức nào.

Nhìn vào ví dụ sau:

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

Ba cách khác nhau, cùng một khái niệm đằng sau:

  • Cách 1
    Sử dụng Delegatelớp đặc biệt trực tiếp như ví dụ trên. Vấn đề của giải pháp này là mã của bạn sẽ không được kiểm tra khi bạn chuyển động các đối số của mình mà không giới hạn chúng theo các kiểu trong định nghĩa phương thức.

  • Cách 2
    Bên cạnh Delegatelớp đặc biệt, khái niệm đại biểu lan truyền đến các đại biểu tùy chỉnh, đó là các định nghĩa về các phương thức đi trước delegatetừ khóa và chúng hoạt động giống như các phương thức thông thường. Do đó, chúng được kiểm tra, vì vậy bạn sẽ đưa ra mã an toàn hoàn hảo.

    Đây là một ví dụ:

    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");
    }
  • Cách 3
    Ngoài ra, bạn có thể sử dụng một đại biểu đã được xác định trong Tiêu chuẩn .NET:

    • Actionkết thúc một voidkhông có đối số.
    • Action<T1>kết thúc một voidvới một đối số.
    • Action<T1, T2>kết thúc một voidvới hai đối số.
    • Và như thế...
    • Func<TR>kết thúc một hàm với TRkiểu trả về và không có đối số.
    • Func<T1, TR>kết thúc một hàm với TRkiểu trả về và với một đối số.
    • Func<T1, T2, TR>kết thúc một hàm với TRkiểu trả về và với hai đối số.
    • Và như thế...

(Giải pháp sau là một trong những người được đăng nhiều nhất.)


1
Không phải kiểu trả về của Func <T> là cuối cùng sao? Func<T1,T2,TR>
sanmcp

13

Bạn nên sử dụng một Func<string, int>đại biểu, đại diện cho một hàm lấy tham stringsố làm đối số và trả về int:

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

Sau đó sử dụng nó:

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

3
Điều này sẽ không được biên dịch. Các Testphương pháp nênreturn RunTheMethod(Method1);
pswg

7

Nếu bạn muốn có khả năng thay đổi phương thức nào được gọi vào thời gian chạy, tôi khuyên bạn nên sử dụng một đại biểu: http://www.codeproject.com/KB/cs/delegates_step1.aspx

Nó sẽ cho phép bạn tạo một đối tượng để lưu trữ phương thức cần gọi và bạn có thể chuyển nó sang các phương thức khác khi cần.


2

Mặc dù câu trả lời được chấp nhận là hoàn toàn chính xác, tôi muốn cung cấp một phương pháp bổ sung.

Tôi đã kết thúc ở đây sau khi tự mình tìm kiếm một giải pháp cho một câu hỏi tương tự. Tôi đang xây dựng một khung điều khiển plugin và như một phần của nó, tôi muốn mọi người có thể thêm các mục menu vào menu ứng dụng vào danh sách chung mà không để lộ Menuđối tượng thực tế vì khung có thể triển khai trên các nền tảng khác không có MenuUI các đối tượng. Thêm thông tin chung về menu là đủ dễ dàng, nhưng cho phép nhà phát triển plugin đủ tự do để tạo cuộc gọi lại khi menu được nhấp vào đã chứng tỏ là một nỗi đau. Cho đến khi tôi nhận ra rằng tôi đang cố gắng phát minh lại bánh xe và các menu thông thường gọi và kích hoạt cuộc gọi lại từ các sự kiện!

Vì vậy, giải pháp, đơn giản như nó nghe khi bạn nhận ra nó, đã lảng tránh tôi cho đến bây giờ.

Chỉ cần tạo các lớp riêng biệt cho từng phương thức hiện tại của bạn, được kế thừa từ một cơ sở nếu bạn phải và chỉ cần thêm một trình xử lý sự kiện cho mỗi phương thức.


1

Dưới đây là một ví dụ có thể giúp bạn hiểu rõ hơn về cách truyền hàm dưới dạng tham số.

Giả sử bạn có trang Parent và bạn muốn mở một cửa sổ bật lên con. Trong trang mẹ có một hộp văn bản cần được điền vào dựa trên hộp văn bản bật lên con.

Ở đây bạn cần tạo một đại biểu.

Parent.cs // khai báo của đại biểu đại biểu công khai void FillName (String FirstName);

Bây giờ hãy tạo một hàm sẽ điền vào hộp văn bản của bạn và hàm sẽ ánh xạ các đại biểu

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

Bây giờ trên nút bấm, bạn cần mở một cửa sổ bật lên Con.

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

         p.Show();

    }

Trình xây dựng IN ChildPopUp bạn cần tạo tham số 'loại ủy nhiệm' của trang //

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

Đã chỉnh sửa nhưng chất lượng của câu trả lời này vẫn có thể được cải thiện.
SteakOverflow

1

Nếu bạn muốn truyền Phương thức làm tham số, hãy sử dụng:

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

Đây là một ví dụ không có tham số: http://en.csharp-online.net/CSharp_FAQ:_How_call_a_method_USE_a_name_opes

với thông số: http://www.daniweb.com/forums/thread98148.html#

về cơ bản bạn truyền vào một mảng các đối tượng cùng với tên của phương thức. Sau đó, bạn sử dụng cả hai với phương thức Gọi.

tham số Object []


Lưu ý rằng tên của phương thức không nằm trong một chuỗi - nó thực sự là một nhóm phương thức. Các đại biểu là câu trả lời tốt nhất ở đây, không phải là sự phản ánh.
Jon Skeet

@Lette: Trong lời gọi phương thức, biểu thức được sử dụng làm đối số là một nhóm phương thức ; đó là tên của một phương thức được biết đến vào thời gian biên dịch và trình biên dịch có thể chuyển đổi nó thành một đại biểu. Điều này rất khác với tình huống mà tên chỉ được biết tại thời điểm thực thi.
Jon Skeet

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

Lớp thứ hai là Client, lớp này sẽ sử dụng lớp lưu trữ. Nó có một phương thức Main tạo ra một thể hiện của PersonDB và nó gọi phương thức Process của đối tượng đó bằng một phương thức được định nghĩa trong lớp Client.

class Client
{
  static void Main()
  {
    PersonDB p = new PersonDB();
    p.Process(PrintName);
  }
  static void PrintName(string name)
  {
    System.Console.WriteLine(name);
  }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.