Passer la méthode comme paramètre à l'aide de C #

694

J'ai plusieurs méthodes toutes avec la même signature (paramètres et valeurs de retour) mais des noms différents et les internes des méthodes sont différents. Je veux transmettre le nom de la méthode à exécuter à une autre méthode qui invoquera la méthode passée.

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

Ce code ne fonctionne pas mais c'est ce que j'essaie de faire. Ce que je ne comprends pas, c'est comment écrire le code RunTheMethod car j'ai besoin de définir le paramètre.

user31673
la source
12
Pourquoi ne passez-vous pas un délégué au lieu du nom de la méthode?
Mark Byers

Réponses:

852

Vous pouvez utiliser le délégué Func dans .net 3.5 comme paramètre dans votre méthode RunTheMethod. Le délégué Func vous permet de spécifier une méthode qui prend un certain nombre de paramètres d'un type spécifique et renvoie un seul argument d'un type spécifique. Voici un exemple qui devrait fonctionner:

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
la source
51
Comment l'appel Func changerait-il si la méthode avait pour signature de renvoyer void et aucun paramètre? Je n'arrive pas à faire fonctionner la syntaxe.
user31673
211
@unknown: Dans ce cas, ce serait Actionau lieu de Func<string, int>.
Jon Skeet
12
mais maintenant si vous voulez passer des arguments à la méthode ??
john ktejik
40
@ user396483 Par exemple, Action<int,string>correspond à une méthode qui prend 2 paramètres (int et chaîne) et renvoie void.
serdar
24
@NoelWidmer Using Func<double,string,int>correspond à une méthode qui prend 2 paramètres ( doubleet string) et retourne int. Le dernier type spécifié est le type de retour. Vous pouvez utiliser ce délégué pour jusqu'à 16 paramètres. Si vous en avez besoin, écrivez votre propre délégué en tant que public delegate TResult Func<in T1, in T2, (as many arguments as you want), in Tn, out TResult>(T1 arg1, T2 arg2, ..., Tn argn);. Veuillez me corriger si j'ai mal compris.
serdar
356

Vous devez utiliser un délégué . Dans ce cas, toutes vos méthodes prennent un stringparamètre et renvoient un int- celui-ci est le plus simplement représenté par le Func<string, int>délégué 1 . Ainsi, votre code peut devenir correct avec un changement aussi simple que celui-ci:

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

Certes, les délégués ont beaucoup plus de pouvoir que cela. Par exemple, avec C #, vous pouvez créer un délégué à partir d'une expression lambda , vous pouvez donc invoquer votre méthode de cette façon:

RunTheMethod(x => x.Length);

Cela créera une fonction anonyme comme celle-ci:

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

puis passez ce délégué à la RunTheMethodméthode.

Vous pouvez utiliser des délégués pour les abonnements aux événements, l'exécution asynchrone, les rappels - toutes sortes de choses. Cela vaut la peine de les lire, surtout si vous souhaitez utiliser LINQ. J'ai un article qui traite principalement des différences entre les délégués et les événements, mais vous pouvez quand même le trouver utile.


1 Ceci est simplement basé sur le Func<T, TResult>type de délégué générique dans le cadre; vous pouvez facilement déclarer le vôtre:

public delegate int MyDelegateType(string value)

puis définissez le paramètre de type à la MyDelegateTypeplace.

Jon Skeet
la source
59
+1 C'est vraiment une réponse étonnante pour démarrer en deux minutes.
David Hall
3
Bien que vous puissiez transmettre la fonction à l'aide de délégués, une approche OO plus traditionnelle consisterait à utiliser le modèle de stratégie.
Paolo
21
@Paolo: Les délégués ne sont qu'une mise en œuvre très pratique du modèle de stratégie où la stratégie en question ne nécessite qu'une seule méthode. Ce n'est pas comme si cela allait à l' encontre du modèle de stratégie - mais c'est beaucoup plus pratique que d'implémenter le modèle à l'aide d'interfaces.
Jon Skeet
5
Les délégués "classiques" (connus depuis .NET 1/2) sont-ils toujours utiles, ou sont-ils complètement obsolètes à cause de Func / Action? De plus, ne manque-t-il pas un mot clé délégué dans votre exemple public **delegate** int MyDelegateType(string value)?
M4N
1
@Martin: Doh! Merci pour le correctif. Édité. Quant à déclarer vos propres délégués - il peut être utile de donner un sens au nom de type, mais j'ai rarement créé mon propre type de délégué depuis .NET 3.5.
Jon Skeet
112

De l'exemple d'OP:

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

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

Vous pouvez essayer Action Delegate! Et puis appelez votre méthode en utilisant

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

Appelez ensuite simplement method

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

Console.WriteLine(InvokeMethod(new Func<string, int>(Method2), "MyString2"));
Zain Ali
la source
4
Merci, cela m'a conduit là où je voulais aller car je voulais une méthode "RunTheMethod" plus générique qui permettrait de multiples paramètres. Btw votre premier InvokeMethodappel lambda devrait être à la RunTheMethodplace
John
1
Comme John, cela m'a vraiment aidé à avoir une méthode générique de déplacement. Merci!
ean5533
2
Vous avez fait ma journée;) Vraiment simple à utiliser et beaucoup plus flexible que la réponse choisie IMO.
Sidewinder94
Existe-t-il un moyen de développer RunTheMethod (() => Method1 ("MyString1")); récupérer une valeur de retour? Idéalement un générique?
Jay
si vous voulez passer des paramètres, sachez-le: 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();
}

Usage:

var ReturnValue = Runner(() => GetUser(99));
kravits88
la source
6
C'est très utile. De cette façon, peut utiliser un ou plusieurs paramètres. Je suppose que la réponse la plus récente est la suivante.
bafsar
Je voudrais ajouter une chose à propos de cette implémentation. Si la méthode que vous allez passer a un type de retour void, vous ne pouvez pas utiliser cette solution.
Imants Volkovs
@ImantsVolkovs Je pense que vous pourriez être en mesure de le modifier pour utiliser une action au lieu d'un Func, et changer la signature pour annuler. Pas sûr à 100%.
Shockwave
2
Existe-t-il un moyen de transmettre les paramètres à la fonction appelée?
Jimmy
17

Pour partager une solution aussi complète que possible, je vais finir par présenter trois façons différentes de faire, mais maintenant je vais partir du principe le plus élémentaire.


Une brève introduction

Toutes les langues qui s'exécutent au-dessus de CLR ( Common Language Runtime ), telles que C #, F # et Visual Basic, fonctionnent sous une machine virtuelle, qui exécute le code à un niveau plus élevé que les langues natives comme C et C ++ (qui se compilent directement sur la machine code). Il s'ensuit que les méthodes ne sont pas une sorte de bloc compilé, mais ce ne sont que des éléments structurés que CLR reconnaît. Ainsi, vous ne pouvez pas penser à passer une méthode en tant que paramètre, car les méthodes ne produisent aucune valeur elles-mêmes, car ce ne sont pas des expressions! Ce sont plutôt des instructions, qui sont définies dans le code CIL généré. Ainsi, vous ferez face au concept de délégué.


Qu'est-ce qu'un délégué?

Un délégué représente un pointeur sur une méthode. Comme je l'ai dit ci-dessus, une méthode n'est pas une valeur, donc il y a une classe spéciale dans les langages CLR Delegate, qui enveloppe n'importe quelle méthode.

Regardez l'exemple suivant:

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

Trois façons différentes, le même concept derrière:

  • Méthode 1
    Utilisez la Delegateclasse spéciale directement comme l'exemple ci-dessus. Le problème de cette solution est que votre code ne sera pas contrôlé lorsque vous passerez dynamiquement vos arguments sans les restreindre aux types de ceux de la définition de méthode.

  • Way 2
    Outre la Delegateclasse spéciale, le concept de délégué se propage aux délégués personnalisés, qui sont des définitions de méthodes précédées par ledelegate mot clé, et ils se comportent de la même manière que les méthodes normales. Ils sont ainsi vérifiés, vous obtiendrez donc un code parfaitement sûr.

    Voici un exemple:

    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");
    }
  • Méthode 3
    Vous pouvez également utiliser un délégué qui a déjà été défini dans la norme .NET:

    • Actiontermine un voidsans arguments.
    • Action<T1>termine un voidavec un argument.
    • Action<T1, T2>termine un voidavec deux arguments.
    • Etc...
    • Func<TR>encapsule une fonction avec un TRtype de retour et sans arguments.
    • Func<T1, TR>encapsule une fonction avec le TRtype de retour et avec un argument.
    • Func<T1, T2, TR> termine une fonction avec TR type de retour et avec deux arguments.
    • Etc...

(Cette dernière solution est celle que la plupart des gens ont publiée.)

Davide Cannizzo
la source
1
Le type de retour d'un Func <T> ne devrait-il pas être le dernier? Func<T1,T2,TR>
sanmcp
13

Vous devez utiliser un Func<string, int>délégué, qui représente une fonction prenant un stringargument as et retournant un int:

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

Ensuite, utilisez-le:

public bool Test() {
    return RunTheMethod(Method1);
}
Bruno Reis
la source
3
Cela ne se compilera pas. La Testméthode devrait êtrereturn RunTheMethod(Method1);
pswg
7

Si vous voulez pouvoir modifier la méthode qui est appelée au moment de l'exécution, je recommanderais d'utiliser un délégué: http://www.codeproject.com/KB/cs/delegates_step1.aspx

Il vous permettra de créer un objet pour stocker la méthode à appeler et vous pouvez le transmettre à vos autres méthodes lorsque cela est nécessaire.

MadcapLaugher
la source
2

Bien que la réponse acceptée soit absolument correcte, je voudrais fournir une méthode supplémentaire.

Je me suis retrouvé ici après avoir fait ma propre recherche d'une solution à une question similaire. Je construis un framework piloté par plugin, et dans le cadre de celui-ci, je voulais que les gens puissent ajouter des éléments de menu au menu des applications à une liste générique sans exposer un Menuobjet réel car le framework peut se déployer sur d'autres plateformes qui n'ont pasMenu interface utilisateur objets. Ajouter des informations générales sur le menu est assez facile, mais donner au développeur du plugin suffisamment de liberté pour créer le rappel lorsque le menu est cliqué s'est avéré être une douleur. Jusqu'à ce que je me rende compte que j'essayais de réinventer la roue et l'appel des menus normaux et déclencher le rappel des événements!

Donc, la solution, aussi simple que cela puisse paraître une fois que vous vous en rendez compte, m'a échappé jusqu'à présent.

Créez simplement des classes distinctes pour chacune de vos méthodes actuelles, héritées d'une base si vous le souhaitez, et ajoutez simplement un gestionnaire d'événements à chacune.

Vacille
la source
1

Voici un exemple qui peut vous aider à mieux comprendre comment passer une fonction en paramètre.

Supposons que vous ayez la page Parent et que vous souhaitiez ouvrir une fenêtre contextuelle enfant. Dans la page parent, il y a une zone de texte qui doit être remplie en fonction de la zone de texte contextuelle enfant.

Ici, vous devez créer un délégué.

Parent.cs // déclaration des délégués délégué public void FillName (String FirstName);

Créez maintenant une fonction qui remplira votre zone de texte et la fonction devrait mapper les délégués

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

Maintenant, cliquez sur le bouton, vous devez ouvrir une fenêtre contextuelle enfant.

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

         p.Show();

    }

Dans le constructeur ChildPopUp, vous devez créer un paramètre de «type délégué» de la page parent //

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;
    }
Solution Shrikant-Divyanet
la source
Modifié mais la qualité de cette réponse pourrait encore être améliorée.
SteakOverflow
1

Si vous souhaitez passer la méthode comme paramètre, utilisez:

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
la source
0

Voici un exemple sans paramètre: http://en.csharp-online.net/CSharp_FAQ:_How_call_a_method_using_a_name_string

avec params: http://www.daniweb.com/forums/thread98148.html#

vous passez essentiellement un tableau d'objets avec le nom de la méthode. vous utilisez ensuite les deux avec la méthode Invoke.

params Paramètres Object []

Jeremy Samuel
la source
Notez que le nom de la méthode n'est pas dans une chaîne - il s'agit en fait d'un groupe de méthodes. Les délégués sont la meilleure réponse ici, pas la réflexion.
Jon Skeet
@Lette: Dans l'appel de méthode, l'expression utilisée comme argument est un groupe de méthodes ; c'est le nom d'une méthode connue au moment de la compilation, et le compilateur peut la convertir en délégué. Ceci est très différent de la situation où le nom n'est connu qu'au moment de l'exécution.
Jon Skeet
0
class PersonDB
{
  string[] list = { "John", "Sam", "Dave" };
  public void Process(ProcessPersonDelegate f)
  {
    foreach(string s in list) f(s);
  }
}

La deuxième classe est Client, qui utilisera la classe de stockage. Il a une méthode Main qui crée une instance de PersonDB, et il appelle la méthode Process de cet objet avec une méthode définie dans la 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
la source