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

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

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, так как мне нужно определить параметр.

14 ответов

Решение

Вы можете использовать делегат 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);
    }
}

Вам нужно использовать делегата. В этом случае все ваши методы принимают 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 вместо.

Вы также можете попробовать Action Delegate!

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

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

 public bool RunTheMethod(Action myMethodName)
 {
      myMethodName();
      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"));

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


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

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


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

Делегат представляет указатель на метод. Поскольку (как я уже говорил выше) метод не является значением, в языках 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/3 Помимо 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");
}

Второй вариант, позволяющий не создавать свой собственный делегат, - использовать один из них, объявленный в системных библиотеках:

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

(Это последнее решение состоит в том, что много людей отправили.)

public static T Runner<T>(Func<T> funcToRun)
{
    //Do stuff before running function as normal
    return funcToRun();
}

Использование:

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

Вы должны использовать 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);
}

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

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

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

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

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

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

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

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

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

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

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

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

Если переданный метод должен принимать один аргумент и возвращать значение,Funcэто лучший способ пойти. Вот пример.

      public int Method1(string)
{
    // Do something
    return 6;
}

public int Method2(string)
{
    // Do something different
    return 5;
}

public bool RunTheMethod(Func<string, int> myMethodName)
{
    // Do stuff
    int i = myMethodName("My String");
    Console.WriteLine(i); // This is just in place of the "Do more stuff"
    return true;
}

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

Прочтите документы здесь

Однако, если ваш метод, переданный в качестве параметра, ничего не возвращает, вы также можете использоватьAction. Он поддерживает до 16 параметров для переданного метода. Вот пример.

      public int MethodToBeCalled(string name, int age)
{
    Console.WriteLine(name + "'s age is" + age);
}

public bool RunTheMethod(Action<string, int> myMethodName)
{
    // Do stuff
    myMethodName("bob", 32); // Expected output: "bob's age is 32"
    return true;
}

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

Прочтите документацию здесь

Вот пример без параметра: 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 [] параметры

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

Второй класс - это Client, который будет использовать класс хранилища. У него есть метод 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);
  }
}

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

Так что вы также можете сделать это:

      public int DoStuff(string stuff)
{
    Console.WriteLine(stuff);
}

public static bool MethodWithDelegate(Func<int> delegate)
{
    ///do stuff
    int i = delegate();
    return i!=0;
}

public static void Main(String[] args)
{
    var answer = MethodWithDelegate(()=> DoStuff("On This random string that the MethodWithDelegate doesn't know about."));
}
Другие вопросы по тегам