Передать метод в качестве параметра, используя C #


696

У меня есть несколько методов с одной и той же сигнатурой (параметры и возвращаемые значения), но разные имена и внутренние методы отличаются. Я хочу передать имя метода для запуска другому методу, который вызовет переданный метод.

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

Этот код не работает, но это то, что я пытаюсь сделать. Чего я не понимаю, так это как написать код RunTheMethod, так как мне нужно определить параметр.


12
Почему бы вам не передать делегат вместо имени метода?
Марк Байерс

Ответы:


855

Вы можете использовать делегат Func в .net 3.5 в качестве параметра в вашем методе RunTheMethod. Делегат Func позволяет вам указать метод, который принимает ряд параметров определенного типа и возвращает один аргумент определенного типа. Вот пример, который должен работать:

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
Как изменится вызов Func, если у метода есть сигнатура возврата void и нет параметров? Я не могу заставить синтаксис работать.
user31673

211
@unknown: в этом случае это будет Actionвместо Func<string, int>.
Джон Скит

12
но что теперь, если вы хотите передать аргументы методу ??
Джон Ktejik

40
@ user396483 Например, Action<int,string>соответствует методу, который принимает 2 параметра (int и string) и возвращает void.
Сердар

24
@NoelWidmer Using Func<double,string,int>соответствует методу, который принимает 2 параметра ( doubleи string) и возвращает int. Последний указанный тип является типом возвращаемого значения. Вы можете использовать этот делегат для 16 параметров. Если вам как-то нужно больше, напишите свой собственный делегат как public delegate TResult Func<in T1, in T2, (as many arguments as you want), in Tn, out TResult>(T1 arg1, T2 arg2, ..., Tn argn);. Пожалуйста, поправьте меня, если я неправильно понял.
Сердар

356

Вам нужно использовать делегата . В этом случае все ваши методы принимают stringпараметр и возвращают int- это наиболее просто представлено Func<string, int>делегатом 1 . Таким образом, ваш код может стать правильным с таким простым изменением, как это:

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

По общему признанию, делегаты обладают гораздо большей властью, чем эта. Например, в C # вы можете создать делегат из лямбда-выражения , чтобы вы могли вызывать свой метод следующим образом:

RunTheMethod(x => x.Length);

Это создаст анонимную функцию, подобную этой:

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

а затем передать этот делегат в RunTheMethodметод.

Вы можете использовать делегатов для подписки на события, асинхронного выполнения, обратных вызовов - все виды вещей. Об этом стоит прочитать, особенно если вы хотите использовать LINQ. У меня есть статья, которая в основном посвящена различиям между делегатами и событиями, но в любом случае вы можете найти ее полезной.


1 Это просто основано на универсальном Func<T, TResult>типе делегата в структуре; Вы можете легко заявить о себе:

public delegate int MyDelegateType(string value)

а затем MyDelegateTypeвместо этого сделайте параметр типа .


59
+1 Это действительно потрясающий ответ, который можно услышать за две минуты.
Дэвид Холл

3
В то время как вы можете передать функцию с помощью делегатов, более традиционный ОО подход будет использовать шаблон стратегии.
Паоло

21
@Paolo: Делегаты - это очень удобная реализация шаблона стратегии, где для рассматриваемой стратегии требуется только один метод. Это не так, как будто это идет вразрез с паттерном стратегии - но это намного удобнее, чем реализовывать паттерн с использованием интерфейсов.
Джон Скит

5
Являются ли «классические» делегаты (как известно из .NET 1/2) все еще полезными, или они полностью устарели из-за Func / Action? Кроме того, нет ли в вашем примере ключевого слова делегата public **delegate** int MyDelegateType(string value)?
M4N

1
@ Мартин: Дох! Спасибо за исправление. Ред. Что касается объявления ваших собственных делегатов - может быть полезно придать имени типа какой-то смысл, но я редко создавал свой собственный тип делегата начиная с .NET 3.5.
Джон Скит

113

Из примера OP:

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

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

Вы можете попробовать Action Delegate! А затем вызвать ваш метод с помощью

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

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

Или

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

Тогда просто вызовите метод

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

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

4
Спасибо, это привело меня туда, куда я хотел пойти, так как я хотел более общий метод RunTheMethod, который позволял бы использовать несколько параметров. Кстати, ваш первый InvokeMethodлямбда-звонок должен быть RunTheMethodвместо этого
Джон

1
Как и Джон, это действительно помогло мне использовать универсальный метод перемещения. Спасибо!
ean5533

2
Вы сделали мой день;) Действительно простым в использовании и гораздо более гибким, чем выбранный ответ IMO.
Sidewinder94

Есть ли способ расширить на RunTheMethod (() => Method1 ("MyString1")); получить возвращаемое значение? В идеале универсальный?
Джей

если вы хотите передать параметры, помните об этом: 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();
}

Применение:

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

6
Это очень удобно. Таким способом можно использовать один или несколько параметров. Я думаю, самый обновленный ответ - это.
Бафсар

Я хотел бы добавить одну вещь об этой реализации. Если метод, который вы собираетесь передать, имеет тип возврата void, вы не можете использовать это решение.
Имант Волков

@ImantsVolkovs Я полагаю, что вы можете изменить это, чтобы использовать Action вместо Func, и изменить подпись на void. Хотя не уверен на 100%.
Shockwave

2
Есть ли способ получить параметры, передаваемые в вызываемую функцию?
Джимми

17

Для того, чтобы поделиться как можно более полным решением, я собираюсь представить три различных способа работы, но теперь я собираюсь начать с самого основного принципа.


Краткое введение

Все языки, которые работают поверх CLR ( Common Language Runtime ), такие как C #, F # и Visual Basic, работают под виртуальной машиной, которая выполняет код на более высоком уровне, чем родные языки, такие как C и C ++ (которые непосредственно компилируются в машину). код). Из этого следует, что методы - это не какой-либо вид скомпилированного блока, а просто структурированные элементы, которые распознает CLR. Таким образом, вы не можете думать о том, чтобы передать метод в качестве параметра, потому что методы сами не производят никаких значений, так как они не являются выражениями! Скорее, это операторы, которые определены в сгенерированном коде CIL. Итак, вы столкнетесь с концепцией делегата.


Кто такой делегат?

Делегат представляет указатель на метод. Как я уже говорил выше, метод не является значением, поэтому в языках CLR есть специальный класс Delegate, который заключает в себе любой метод.

Посмотрите на следующий пример:

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

Три разных способа, одна и та же концепция:

  • Способ 1
    Используйте Delegateспециальный класс напрямую, как в примере выше. Проблема этого решения заключается в том, что ваш код не будет проверяться при динамической передаче аргументов, не ограничивая их типами, указанными в определении метода.

  • Способ 2
    Помимо Delegateспециального класса, концепция делегата распространяется на пользовательские делегаты, которые являются определениями методов, которым предшествует delegateключевое слово, и ведут себя так же, как обычные методы. Тем самым они проверяются, поэтому вы получите безупречно безопасный код.

    Вот пример:

    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");
    }
  • Способ 3 В
    качестве альтернативы, вы можете использовать делегата, который уже был определен в .NET Standard:

    • Actionоборачивает voidбез аргументов.
    • Action<T1>оборачивает voidс одним аргументом.
    • Action<T1, T2>оборачивает voidс двумя аргументами.
    • И так далее...
    • Func<TR>оборачивает функцию с TRтипом возврата и без аргументов.
    • Func<T1, TR>оборачивает функцию с TRтипом возврата и с одним аргументом.
    • Func<T1, T2, TR>оборачивает функцию TRвозвращаемым типом и двумя аргументами.
    • И так далее...

(Последнее решение - то, которое большинство людей отправило.)


1
Разве тип возврата Func <T> не должен быть последним? Func<T1,T2,TR>
sanmcp

13

Вы должны использовать Func<string, int>делегат, который представляет функцию, принимающую в stringкачестве аргумента и возвращающую int:

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

Тогда используйте это:

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

3
Это не скомпилируется. TestМетод должен бытьreturn RunTheMethod(Method1);
PSWG

7

Если вы хотите изменить способ вызова метода во время выполнения, я бы порекомендовал использовать делегата: http://www.codeproject.com/KB/cs/delegates_step1.aspx.

Это позволит вам создать объект для хранения вызываемого метода, и вы можете передать его другим методам, когда это необходимо.


2

Хотя принятый ответ является абсолютно правильным, я хотел бы предоставить дополнительный метод.

Я попал сюда после того, как сам занялся поиском решения аналогичного вопроса. Я создаю платформу, управляемую плагином, и как часть этого я хотел, чтобы люди могли добавлять пункты меню в меню приложений в общий список, не раскрывая фактический Menuобъект, потому что платформа может развертываться на других платформах, которые не имеют Menuпользовательского интерфейса объекты. Добавление общей информации о меню достаточно просто, но предоставление разработчику плагина достаточной свободы для создания обратного вызова, когда при щелчке по меню оказывалось трудным делом. Пока меня не осенило, что я пытался заново изобрести колесо и обычное меню вызова и вызвать обратный вызов от событий!

Таким образом, решение, настолько простое, насколько это звучит, когда вы его осознаете, ускользало от меня до сих пор.

Просто создайте отдельные классы для каждого из ваших текущих методов, унаследованных от базы, если необходимо, и просто добавьте обработчик событий для каждого из них.


1

Вот пример, который может помочь вам лучше понять, как передать функцию в качестве параметра.

Предположим, у вас есть родительская страница, и вы хотите открыть дочернее всплывающее окно. На родительской странице есть текстовое поле, которое должно быть заполнено на основе дочернего всплывающего текстового поля.

Здесь вам нужно создать делегата.

Parent.cs // объявление делегатов публичный делегат void FillName (String FirstName);

Теперь создайте функцию, которая заполнит ваше текстовое поле, и функция должна отображать делегатов.

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

Теперь при нажатии кнопки вам нужно открыть всплывающее окно Child.

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

         p.Show();

    }

В конструкторе ChildPopUp необходимо создать параметр «тип делегата» родительской // страницы

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

Отредактировано, но качество этого ответа еще можно улучшить.
SteakOverflow

1

Если вы хотите передать метод в качестве параметра, используйте:

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

Вот пример без параметра: http://en.csharp-online.net/CSharp_FAQ:_How_call_a_method_using_a_name_string

с параметрами: http://www.daniweb.com/forums/thread98148.html#

вы в основном передаете массив объектов вместе с именем метода. Затем вы используете оба метода Invoke.

params Object [] параметры


Обратите внимание, что имя метода не в строке - это фактически группа методов. Делегаты здесь лучший ответ, а не рефлексия.
Джон Скит

@Lette: в вызове метода выражение, используемое в качестве аргумента, является группой методов ; это имя метода, которое известно во время компиляции, и компилятор может преобразовать его в делегат. Это сильно отличается от ситуации, когда имя известно только во время выполнения.
Джон Скит

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

Второй класс - это Клиент, который будет использовать класс хранения. У него есть метод Main, который создает экземпляр PersonDB, и он вызывает метод Process этого объекта с методом, который определен в классе Client.

class Client
{
  static void Main()
  {
    PersonDB p = new PersonDB();
    p.Process(PrintName);
  }
  static void PrintName(string name)
  {
    System.Console.WriteLine(name);
  }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.