Передать метод в качестве параметра, используя 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."));
}