Pasar método como parámetro usando C #

694

Tengo varios métodos, todos con la misma firma (parámetros y valores de retorno) pero diferentes nombres y aspectos internos de los métodos son diferentes. Quiero pasar el nombre del método a otro método que invoque el método pasado.

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

Este código no funciona, pero esto es lo que estoy tratando de hacer. Lo que no entiendo es cómo escribir el código RunTheMethod ya que necesito definir el parámetro.

usuario31673
fuente
12
¿Por qué no pasa un delegado en lugar del nombre del método?
Mark Byers

Respuestas:

852

Puede usar el delegado Func en .net 3.5 como parámetro en su método RunTheMethod. El delegado de Func le permite especificar un método que toma una serie de parámetros de un tipo específico y devuelve un único argumento de un tipo específico. Aquí hay un ejemplo que debería funcionar:

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);
    }
}
Egil Hansen
fuente
51
¿Cómo cambiaría la llamada de Func si el Método tiene como firma la devolución del vacío y ningún parámetro? Parece que no puedo hacer que la sintaxis funcione.
user31673
210
@unknown: en ese caso sería en Actionlugar de Func<string, int>.
Jon Skeet
12
pero ahora ¿qué pasa si quieres pasar argumentos al método?
John Ktejik
40
@ user396483 Por ejemplo, Action<int,string>corresponde a un método que toma 2 parámetros (int y string) y devuelve void.
Serdar
24
@NoelWidmer Using Func<double,string,int>corresponde a un método que toma 2 parámetros ( doubley string) y regresa int. El último tipo especificado es el tipo de retorno. Puede usar este delegado para hasta 16 parámetros. Si de alguna manera necesita más, escriba su propio delegado como public delegate TResult Func<in T1, in T2, (as many arguments as you want), in Tn, out TResult>(T1 arg1, T2 arg2, ..., Tn argn);. Por favor, corrígeme si no he entendido bien.
Serdar
356

Necesitas usar un delegado . En este caso, todos sus métodos toman un stringparámetro y devuelven un int- esto es simplemente representado por el Func<string, int>delegado 1 . Por lo tanto, su código puede ser correcto con un cambio tan simple como este:

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

Los delegados tienen mucho más poder que esto, es cierto. Por ejemplo, con C # puede crear un delegado a partir de una expresión lambda , por lo que puede invocar su método de esta manera:

RunTheMethod(x => x.Length);

Eso creará una función anónima como esta:

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

y luego pasa ese delegado al RunTheMethodmétodo.

Puede usar delegados para suscripciones de eventos, ejecución asincrónica, devoluciones de llamada, todo tipo de cosas. Vale la pena leerlos, especialmente si desea usar LINQ. Tengo un artículo que trata principalmente sobre las diferencias entre delegados y eventos, pero de todos modos puede resultarle útil.


1 Esto se basa únicamente en el Func<T, TResult>tipo de delegado genérico en el marco; podrías declarar fácilmente el tuyo:

public delegate int MyDelegateType(string value)

y luego haga que el parámetro sea de tipo MyDelegateType.

Jon Skeet
fuente
59
+1 Esta es realmente una respuesta increíble para recitar en dos minutos.
David Hall el
3
Si bien puede pasar la función usando delegados, un enfoque OO más tradicional sería usar el patrón de estrategia.
Paolo
20
@Paolo: Los delegados son solo una implementación muy conveniente del patrón de estrategia donde la estrategia en cuestión solo requiere un método único. No es que esto vaya en contra del patrón estratégico, pero es muchísimo más conveniente que implementar el patrón usando interfaces.
Jon Skeet
55
¿Siguen siendo útiles los delegados "clásicos" (como se conoce en .NET 1/2) o son completamente obsoletos debido a Func / Action? Además, ¿no falta una palabra clave delegada en su ejemplo public **delegate** int MyDelegateType(string value)?
M4N
1
@ Martin: Doh! Gracias por el arreglo. Editado En cuanto a declarar sus propios delegados, puede ser útil darle un cierto significado al nombre del tipo, pero rara vez he creado mi propio tipo de delegado desde .NET 3.5.
Jon Skeet
112

Del ejemplo de OP:

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

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

¡Puedes probar Action Delegate! Y luego llame a su método usando

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

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

O

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

Entonces simplemente llame al método

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

Console.WriteLine(InvokeMethod(new Func<string, int>(Method2), "MyString2"));
Zain Ali
fuente
44
Gracias, esto me llevó a donde quería ir, ya que quería un método más genérico "RunTheMethod" que permitiera múltiples parámetros. Por cierto, su primera InvokeMethodllamada lambda debería ser RunTheMethoden su lugar
John
1
Al igual que John, esto realmente me ayudó a tener un método genérico de movimiento. ¡Gracias!
ean5533
2
Me alegraste el día;) Muy simple de usar y mucho más flexible que la respuesta seleccionada OMI.
Sidewinder94
¿Hay alguna manera de expandirse en RunTheMethod (() => Method1 ("MyString1")); recuperar un valor de retorno? Idealmente un genérico?
Jay
si desea pasar parámetros, tenga en cuenta esto: 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();
}

Uso:

var ReturnValue = Runner(() => GetUser(99));
kravits88
fuente
66
Es muy útil. De esta manera, puede usar uno o muchos parámetros. Supongo que la respuesta más actualizada es esta.
bafsar
Me gustaría agregar una cosa sobre esta implementación. Si el método que va a pasar tiene un tipo de retorno nulo, entonces no puede usar esta solución.
Imants Volkovs
@ImantsVolkovs Creo que es posible que pueda modificar esto para usar una Acción en lugar de un Func, y cambiar la firma a nula. Aunque no estoy 100% seguro.
Shockwave
2
¿Hay alguna forma de pasar los parámetros a la función que se llama?
Jimmy
16

Para compartir una solución lo más completa posible, terminaré presentando tres formas diferentes de hacerlo, pero ahora voy a comenzar desde el principio más básico.


Una breve introduccion

Todos los lenguajes que se ejecutan sobre CLR ( Common Language Runtime ), como C #, F # y Visual Basic, funcionan en una VM, que ejecuta el código en un nivel superior a los lenguajes nativos como C y C ++ (que se compilan directamente en la máquina código). Se deduce que los métodos no son ningún tipo de bloque compilado, sino que son elementos estructurados que CLR reconoce. Por lo tanto, no puede pensar en pasar un método como parámetro, ¡porque los métodos no producen ningún valor por sí mismos, ya que no son expresiones! Más bien, son declaraciones, que se definen en el código CIL generado. Entonces, te enfrentarás al concepto de delegado.


¿Qué es un delegado?

Un delegado representa un puntero a un método. Como dije anteriormente, un método no es un valor, por lo tanto, hay una clase especial en lenguajes CLR Delegate, que envuelve cualquier método.

Mira el siguiente ejemplo:

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

Tres formas diferentes, el mismo concepto detrás:

  • Forma 1
    Use la Delegateclase especial directamente como el ejemplo anterior. El problema de esta solución es que su código estará desmarcado a medida que pase dinámicamente sus argumentos sin restringirlos a los tipos de aquellos en la definición del método.

  • Modo 2
    Además de la Delegateclase especial, el concepto de delegado se extiende a delegados personalizados, que son definiciones de métodos precedidos por la delegatepalabra clave, y se comportan igual que los métodos normales. Por lo tanto, se verifican, por lo que obtendrá un código impecablemente seguro.

    Aquí hay un ejemplo:

    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");
    }
  • Modo 3
    Alternativamente, puede usar un delegado que ya ha sido definido dentro del estándar .NET:

    • Actionconcluye un voidsin argumentos.
    • Action<T1>concluye un voidcon un argumento.
    • Action<T1, T2>concluye a voidcon dos argumentos.
    • Y así...
    • Func<TR>concluye una función con TRtipo de retorno y sin argumentos.
    • Func<T1, TR>concluye una función con el TRtipo de retorno y con un argumento.
    • Func<T1, T2, TR>concluye una función con TRtipo de retorno y con dos argumentos.
    • Y así...

(La última solución es la que la mayoría de la gente publicó).

Davide Cannizzo
fuente
1
¿No debería ser el último tipo de retorno de un Func <T>? Func<T1,T2,TR>
sanmcp
13

Debe usar un Func<string, int>delegado, que representa una función que toma un stringargumento y devuelve un int:

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

Entonces úsalo:

public bool Test() {
    return RunTheMethod(Method1);
}
Bruno Reis
fuente
3
Esto no se compilará. El Testmétodo debería serreturn RunTheMethod(Method1);
pswg
7

Si desea cambiar el método que se llama en tiempo de ejecución, le recomendaría usar un delegado: http://www.codeproject.com/KB/cs/delegates_step1.aspx

Le permitirá crear un objeto para almacenar el método para llamar y puede pasarlo a sus otros métodos cuando sea necesario.

MadcapLaugher
fuente
2

Si bien la respuesta aceptada es absolutamente correcta, me gustaría proporcionar un método adicional.

Terminé aquí después de hacer mi propia búsqueda de una solución a una pregunta similar. Estoy construyendo un marco basado en complementos, y como parte de él quería que las personas pudieran agregar elementos de menú al menú de aplicaciones a una lista genérica sin exponer un Menuobjeto real porque el marco puede implementarse en otras plataformas que no tienen Menuinterfaz de usuario objetos. Agregar información general sobre el menú es bastante fácil, pero permitir que el desarrollador del complemento tenga la libertad suficiente para crear la devolución de llamada para cuando se hace clic en el menú resultó ser una molestia. ¡Hasta que caí en la cuenta de que estaba tratando de reinventar la rueda y los menús normales llaman y activan la devolución de llamada de los eventos!

Entonces, la solución, tan simple como suena una vez que te das cuenta, me eludió hasta ahora.

Simplemente cree clases separadas para cada uno de sus métodos actuales, heredados de una base si es necesario, y simplemente agregue un controlador de eventos a cada uno.

Tambaleo
fuente
1

Aquí hay un ejemplo que puede ayudarlo a comprender mejor cómo pasar una función como parámetro.

Suponga que tiene Padres página y desea abrir una ventana emergente niño. En la página principal hay un cuadro de texto que debe rellenarse basándose en el cuadro de texto emergente secundario.

Aquí necesitas crear un delegado.

Parent.cs // declaración de delegados public delegate void FillName (String FirstName);

Ahora cree una función que llene su cuadro de texto y la función debería asignar delegados

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

Ahora, al hacer clic en el botón, debe abrir una ventana emergente secundaria.

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

         p.Show();

    }

En el constructor ChildPopUp, debe crear el parámetro de 'tipo de delegado' de la página principal //

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;
    }
Solución Shrikant-Divyanet
fuente
Editado pero la calidad de esta respuesta aún podría mejorarse.
SteakOverflow
1

Si desea pasar Método como parámetro, use:

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");
}
Juned Khan Momin
fuente
0

Aquí hay un ejemplo sin parámetro: http://en.csharp-online.net/CSharp_FAQ:_How_call_a_method_using_a_name_string

con parámetros: http://www.daniweb.com/forums/thread98148.html#

básicamente pasa una matriz de objetos junto con el nombre del método. luego usa ambos con el método Invoke.

parámetros de Object [] params

Jeremy Samuel
fuente
Tenga en cuenta que el nombre del método no está en una cadena, en realidad es como un grupo de métodos. Los delegados son la mejor respuesta aquí, no la reflexión.
Jon Skeet
@Lette: en la invocación del método, la expresión utilizada como argumento es un grupo de métodos ; es el nombre de un método que se conoce en tiempo de compilación, y el compilador puede convertirlo en un delegado. Esto es muy diferente de la situación en la que el nombre solo se conoce en el momento de la ejecución.
Jon Skeet
0
class PersonDB
{
  string[] list = { "John", "Sam", "Dave" };
  public void Process(ProcessPersonDelegate f)
  {
    foreach(string s in list) f(s);
  }
}

La segunda clase es Client, que utilizará la clase de almacenamiento. Tiene un método Main que crea una instancia de PersonDB, y llama al método Process de ese objeto con un método que se define en la clase Client.

class Client
{
  static void Main()
  {
    PersonDB p = new PersonDB();
    p.Process(PrintName);
  }
  static void PrintName(string name)
  {
    System.Console.WriteLine(name);
  }
}
x-rw
fuente