Passar método como parâmetro usando c #

694

Eu tenho vários métodos, todos com a mesma assinatura (parâmetros e valores de retorno), mas nomes diferentes e os elementos internos dos métodos são diferentes. Quero passar o nome do método para executar em outro método que invocará o método passado.

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 não funciona, mas é isso que estou tentando fazer. O que não entendo é como escrever o código RunTheMethod, pois preciso definir o parâmetro.

user31673
fonte
12
Por que você não passa um delegado em vez do nome do método?
precisa

Respostas:

852

Você pode usar o representante Func no .net 3.5 como o parâmetro no seu método RunTheMethod. O delegado Func permite especificar um método que utiliza vários parâmetros de um tipo específico e retorna um único argumento de um tipo específico. Aqui está um exemplo que deve 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
fonte
51
Como a chamada Func mudaria se o método tivesse como assinatura o retorno de nulos e sem parâmetros? Parece que não consigo fazer com que a sintaxe funcione.
precisa saber é o seguinte
210
@ desconhecido: Nesse caso, seria em Actionvez de Func<string, int>.
precisa saber é o seguinte
12
mas agora e se você quiser passar argumentos para o método?
31414 John John Ktejik em
40
@ user396483 Por exemplo, Action<int,string>corresponde a um método que utiliza 2 parâmetros (int e string) e retorna nulo.
serdar 12/06
24
@NoelWidmer Using Func<double,string,int>corresponde a um método que recebe 2 parâmetros ( doublee string) e retorna int. O último tipo especificado é o tipo de retorno. Você pode usar esse delegado para até 16 parâmetros. Se você precisar de mais alguma coisa, escreva seu próprio representante 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, corrija-me se eu entendi errado.
serdar 11/09/14
356

Você precisa usar um delegado . Nesse caso, todos os seus métodos usam um stringparâmetro e retornam um int- isto é representado simplesmente pelo Func<string, int>delegado 1 . Portanto, seu código pode se tornar correto com uma alteração tão simples quanto esta:

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

Os delegados têm muito mais poder do que isso, é certo. Por exemplo, com C #, você pode criar um delegado a partir de uma expressão lambda , para poder invocar seu método da seguinte maneira:

RunTheMethod(x => x.Length);

Isso criará uma função 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;
}

e depois passe esse delegado para o RunTheMethodmétodo

Você pode usar delegados para inscrições de eventos, execução assíncrona, retornos de chamada - todos os tipos de coisas. Vale a pena ler sobre eles, principalmente se você quiser usar o LINQ. Eu tenho um artigo que trata principalmente das diferenças entre delegados e eventos, mas você pode achar útil de qualquer maneira.


1 Isso se baseia apenas no Func<T, TResult>tipo de delegado genérico na estrutura; você pode facilmente declarar o seu:

public delegate int MyDelegateType(string value)

e faça com que o parâmetro seja do tipo MyDelegateType.

Jon Skeet
fonte
59
+1 Esta é realmente uma resposta incrível para recitar em dois minutos.
David Hall
3
Embora você possa passar a função usando delegados, uma abordagem OO mais tradicional seria usar o padrão de estratégia.
Paolo
20
@Paolo: Os delegados são apenas uma implementação muito conveniente do padrão de estratégia, onde a estratégia em questão requer apenas um único método. Não é como se isso fosse contrário ao padrão de estratégia - mas é muito mais conveniente do que implementar o padrão usando interfaces.
precisa saber é o seguinte
5
Os delegados "clássicos" (conhecidos no .NET 1/2) ainda são úteis ou são completamente obsoletos por causa do Func / Action? Além disso, não há uma palavra-chave delegada ausente no seu exemplo public **delegate** int MyDelegateType(string value)?
M4N
1
@ Martin: Doh! Obrigado pela correção. Editado. Quanto à declaração de seus próprios representantes - pode ser útil dar algum significado ao nome do tipo, mas raramente criei meu próprio tipo de representante desde o .NET 3.5.
quer
112

Do exemplo do OP:

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

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

Você pode experimentar o Delegado de Ação! E então chame seu método usando

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

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

Ou

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

Em seguida, basta chamar o método

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

Console.WriteLine(InvokeMethod(new Func<string, int>(Method2), "MyString2"));
Zain Ali
fonte
4
Obrigado, isso me levou aonde eu queria ir, pois queria um método "RunTheMethod" mais genérico que permitisse vários parâmetros. Btw sua primeira InvokeMethodchamada lambda deve ser RunTheMethodem vez
John
1
Como John, isso realmente me ajudou a ter um método genérico de movimentação. Obrigado!
ean5533
2
Você fez o meu dia;) Realmente simples de usar e muito mais flexível que a resposta selecionada IMO.
precisa
Existe uma maneira de expandir o RunTheMethod (() => Method1 ("MyString1")); recuperar um valor de retorno? Idealmente um genérico?
Jay
se você deseja passar parâmetros, esteja ciente disso: 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
fonte
6
É muito fácil de usar. Desta forma, pode usar um ou vários parâmetros. Eu acho que a resposta mais atualizada é essa.
bafsar
Eu gostaria de adicionar uma coisa sobre esta implementação. Se o método a ser aprovado tiver um tipo de retorno nulo, não será possível usar esta solução.
Imants Volkovs
@ImantsVolkovs Acredito que você possa modificá-lo para usar uma Ação em vez de um Func e alterar a assinatura para anular. Não é 100% certo.
Shockwave
2
Existe alguma maneira de obter os parâmetros passados ​​para a função que é chamada?
Jimmy Jimmy
16

Por compartilhar uma solução o mais completa possível, vou apresentar três maneiras diferentes de fazer, mas agora vou começar pelo princípio mais básico.


Uma breve introdução

Todos os idiomas executados no CLR ( Common Language Runtime ), como C #, F # e Visual Basic, funcionam em uma VM, que executa o código em um nível superior aos idiomas nativos como C e C ++ (que são compilados diretamente na máquina). código). Daqui resulta que os métodos não são nenhum tipo de bloco compilado, mas são apenas elementos estruturados que o CLR reconhece. Portanto, você não pode passar um método como parâmetro, porque os métodos não produzem nenhum valor, pois não são expressões! Em vez disso, são instruções definidas no código CIL gerado. Então, você enfrentará o conceito de delegado.


O que é um delegado?

Um delegado representa um ponteiro para um método. Como eu disse acima, um método não é um valor, portanto, há uma classe especial nas linguagens CLR Delegate, que agrupa qualquer método.

Veja o seguinte exemplo:

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

Três maneiras diferentes, o mesmo conceito por trás:

  • Caminho 1
    Use a Delegateclasse especial diretamente como no exemplo acima. O problema desta solução é que seu código será desmarcado quando você passar dinamicamente seus argumentos sem restringi-los aos tipos daqueles na definição do método.

  • Caminho 2
    Além doDelegate classe especial, o conceito de delegado se espalha para delegados personalizados, que são definições de métodos precedidos pela delegatepalavra - chave e se comportam da mesma forma que os métodos normais. Eles são verificados para que você tenha um código perfeitamente seguro.

    Aqui está um exemplo:

    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");
    }
  • Caminho 3
    Como alternativa, você pode usar um representante que já foi definido no .NET Standard:

    • Actionquebra um voidsem argumentos.
    • Action<T1>termina um voidcom um argumento.
    • Action<T1, T2> envolve um void com dois argumentos.
    • E assim por diante...
    • Func<TR>agrupa uma função com o TRtipo de retorno e sem argumentos.
    • Func<T1, TR>agrupa uma função com o TRtipo de retorno e com um argumento.
    • Func<T1, T2, TR> envolve uma função com TR tipo de retorno e com dois argumentos.
    • E assim por diante...

(A última solução é a que mais pessoas publicaram.)

Davide Cannizzo
fonte
1
O tipo de retorno de um Func <T> não deveria ser o último? Func<T1,T2,TR>
Sanmcp 13/05/19
13

Você deve usar um Func<string, int>delegado, que representa uma função que recebe um stringargumento as e retorna um int:

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

Então use-o:

public bool Test() {
    return RunTheMethod(Method1);
}
Bruno Reis
fonte
3
Isso não será compilado. O Testmétodo deve serreturn RunTheMethod(Method1);
pswg
7

Se você quiser alterar o método chamado em tempo de execução, recomendo o uso de um representante: http://www.codeproject.com/KB/cs/delegates_step1.aspx

Isso permitirá que você crie um objeto para armazenar o método a ser chamado e você poderá transmiti-lo aos outros métodos quando necessário.

MadcapLaugher
fonte
2

Embora a resposta aceita esteja absolutamente correta, eu gostaria de fornecer um método adicional.

Acabei aqui depois de fazer minha própria busca de uma solução para uma pergunta semelhante. Estou criando uma estrutura orientada a plug-ins e, como parte dela, queria que as pessoas pudessem adicionar itens de menu ao menu de aplicativos em uma lista genérica sem expor um Menuobjeto real, porque a estrutura pode ser implantada em outras plataformas que não possuem Menuinterface do usuário objetos. A adição de informações gerais sobre o menu é bastante fácil, mas permitir ao desenvolvedor do plugin liberdade suficiente para criar o retorno de chamada para quando o menu é clicado estava provando ser um problema. Até que me dei conta de que estava tentando reinventar a chamada do volante e dos menus normais e acionar o retorno de chamada dos eventos!

Portanto, a solução, tão simples quanto parece depois que você a percebe, me escapou até agora.

Apenas crie classes separadas para cada um dos métodos atuais, herdadas de uma base, se necessário, e adicione um manipulador de eventos a cada uma.

Wobbles
fonte
1

Aqui está um exemplo que pode ajudá-lo a entender melhor como passar uma função como parâmetro.

Suponha que você tenha a página pai e deseje abrir uma janela pop-up filho. Na página pai, há uma caixa de texto que deve ser preenchida com base na caixa de texto pop-up filho.

Aqui você precisa criar um delegado.

Parent.cs // declaração dos delegados delegado público void FillName (String FirstName);

Agora crie uma função que preencha sua caixa de texto e a função deve mapear os delegados

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

Agora, clique no botão, você precisa abrir uma janela pop-up filho.

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

         p.Show();

    }

No construtor ChildPopUp, você precisa criar o parâmetro 'tipo delegado' da página pai //

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;
    }
Solução Shrikant-Divyanet
fonte
Editado, mas a qualidade desta resposta ainda pode ser melhorada.
SteakOverflow
1

Se você deseja passar o 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
fonte
0

Aqui está um exemplo sem um parâmetro: http://en.csharp-online.net/CSharp_FAQ:_How_call_a_method_using_a_name_string

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

você basicamente passa uma matriz de objetos junto com o nome do método. você usa os dois com o método Invoke.

params Parâmetros do objeto []

Jeremy Samuel
fonte
Observe que o nome do método não está em uma string - na verdade, é um grupo de métodos. Os delegados são a melhor resposta aqui, não a reflexão.
precisa
@ Lette: Na invocação do método, a expressão usada como argumento é um grupo de métodos ; é o nome de um método conhecido no momento da compilação, e o compilador pode convertê-lo em um delegado. Isso é muito diferente da situação em que o nome é conhecido apenas no tempo de execução.
Jon Skeet
0
class PersonDB
{
  string[] list = { "John", "Sam", "Dave" };
  public void Process(ProcessPersonDelegate f)
  {
    foreach(string s in list) f(s);
  }
}

A segunda classe é o cliente, que usará a classe de armazenamento. Ele possui um método Main que cria uma instância do PersonDB e chama o método Process desse objeto com um método definido na classe Client.

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