Метод передачі як параметр, використовуючи C #


694

У мене є кілька методів, які мають однакову підпис (параметри і значення повернення), але різні назви та внутрішні методи методів різні. Я хочу передати ім'я методу для запуску іншому методу, який викликатиме передане в методі.

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
Чому ви не передасте делегата замість імені методу?
Марк Байєрс

Відповіді:


852

Ви можете використовувати делегат 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, якщо у методу є підпис повернення пустоти і немає параметрів? Я, здається, не можу змусити синтаксис працювати.
користувач31673

211
@unknown: У такому випадку це було б Actionзамість Func<string, int>.
Джон Скіт

12
але що робити, якщо ви хочете передати аргументи методу ??
John ktejik

40
@ user396483 Наприклад, Action<int,string>відповідає методу, який приймає 2 параметри (int і string) і повертає void.
сердар

24
@NoelWidmer Використання 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параметр і повертають an 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
@Martin: До! Дякуємо за виправлення. Відредаговано. Що стосується оголошення власних делегатів - це може бути корисно надати імені типу деяке значення, але я рідко створював свій власний тип делегата з .NET 3.5.
Джон Скіт

112

З прикладу ОП:

 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", який дозволив би отримати декілька параметрів. Btw ваш перший InvokeMethodлямбда-дзвінок повинен бути RunTheMethodзамість цього
Іван

1
Як і Джон, це дійсно допомогло мені зробити загальний метод руху. Дякую!
ean5533

2
Ви зробили мій день;) Дійсно простий у використанні та набагато гнучкіший, ніж обрана відповідь ІМО.
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
Це дуже корисно. Таким чином можна використовувати один або багато параметрів. Я здогадуюсь, найбільш оновлена ​​відповідь така.
бафсар

Я хотів би додати одне про цю реалізацію. Якщо метод, який ви збираєтеся пройти, має тип повернення недійсним, ви не можете використовувати це рішення.
Іманц Волковс

@ImantsVolkovs Я вважаю, що ви можете змінити це для використання Action замість Func та змінити підпис до недійсної. Не на 100% впевнений.
Shockwave

2
Чи є спосіб передати параметри функції, яка називається?
Джиммі

17

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


Короткий вступ

Усі мови, що працюють поверх CLR ( Common Language Runtime ), такі як C #, F # і Visual Basic, працюють під VM, який запускає код на більш високому рівні, ніж рідні мови, такі як 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типом return і без аргументів.
    • 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 // оголошення делегатів публічним делегатом недійсним FillName (String FirstName);

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

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

Тепер при натисканні кнопки потрібно відкрити спливаюче вікно.

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

         p.Show();

    }

У конструкторі Child 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 об'єкта методом, визначеним у класі клієнта.

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.