Attribuer du code à une variable

124

Est-il possible de créer une variable et de lui attribuer une ligne de code, telle que:

ButtonClicked = (MessageBox.Show("Hello, World!"));

... donc quand j'utilise la variable, elle exécutera la ligne de code.

user3539891
la source
100
+1 pour la rare combinaison d'être nouveau dans le codage et de poser une bonne question: vous comprenez ce que vous voulez faire et l'expliquez bien, vous ne connaissez tout simplement pas le terme pour cela, vous ne pouvez donc pas le trouver par vous-même.
Tim S.
10
Le terme que vous recherchez est un délégué .
Lasse V. Karlsen
stackoverflow.com/questions/6187944/… vérifiez ceci, je pense qu'il y a suffisamment d'explications dont vous aurez besoin. Asp fonctionne presque comme des winforms dans ce domaine.
CSharpie
Cela ressemble beaucoup à des blocs dans Objective-c
Brian Tracy

Réponses:

89

Vous pouvez l'attribuer à un Actioncomme ceci:

var ButtonClicked = new Action(() => MessageBox.Show("hi"));

Alors appelez-le:

ButtonClicked();

Par souci d'exhaustivité (en ce qui concerne les différents commentaires) ...

Comme l'a déclaré Erik, vous pouvez exécuter plusieurs lignes de code:

var ButtonClicked = new Action(() =>
{
    MessageBox.Show("hi");

    MessageBox.Show("something else");  // something more useful than another popup ;)
});

Comme Tim l'a dit, vous pouvez omettre le Actionmot - clé

Action ButtonClicked = () => MessageBox.Show("hi");

Action ButtonClicked = () =>
{
    // multiple lines of code
};

Pour répondre au commentaire de KRyan, concernant les parenthèses vides, qui représente la liste des paramètres que vous voulez pouvoir envoyer à l'action (dans ce cas, aucun) .

Si, par exemple, que vous vouliez spécifier le message à afficher, vous pouvez ajouter « message » en tant que paramètre (notez que je l' ai changé Action pour afin de spécifier un seul paramètre de chaîne) :Action<string>

Action<string> ButtonClicked = (message) => MessageBox.Show(message);

ButtonClicked("hello world!");
poissons vivat
la source
10
Action ButtonClicked = () => MessageBox.Show("hi");est équivalent et IMO plus agréable (ajoutez des parens si vous préférez)
Tim S.
1
Il est également possible que l'action se résout à plus d'une seule ligne de code.
Erik Philips
2
@CSharpie Je ne suis pas sûr que cette hypothèse soit utile pour l'OP.
Erik Philips
2
@CSharpie Pourquoi l'OP n'a-t-il pas pu l'utiliser WinForms?
vivat pisces
2
@CSharpie Je vois ce que vous dites. S'il l'attache réellement à un Button.Clickévénement et ne le stocke pas dans une variable qu'il a nommée ButtonClicked.
vivat pisces
51

Dans votre cas, vous souhaitez utiliser un fichier delegate.

Voyons comment fonctionne un délégué et comment nous pouvons obtenir une forme plus simple en comprenant son concept:

// Create a normal function
void OnButtonClick()
{
    MessageBox.Show("Hello World!");
}
// Now we create a delegate called ButtonClick
delegate void ButtonClick();

Vous voyez, le délégué prend la forme d'une fonction normale mais sans aucun argument (il peut prendre n'importe quelle quantité d'arguments comme n'importe quelle autre méthode, mais pour des raisons de simplicité, ce n'est pas le cas).

Maintenant, utilisons ce que nous avons; nous définirons le délégué comme nous définissons toute autre variable:

ButtonClick ButtonClicked = new ButtonClick(OnButtonClick);

Nous avons essentiellement créé une nouvelle variable appelée ButtonClicked, qui a un type de ButtonClick (qui est un délégué) et qui, lorsqu'elle est utilisée, exécutera la méthode dans la méthode OnButtonClick ().
Pour l'utiliser, nous appelons simplement:ButtonClicked();

Donc, tout le code serait:

delegate void ButtonClick();

void OnButtonClick()
{
    MessageBox.Show("Hello World!");
}

void Foo()
{
    ButtonClick ButtonClicked = new ButtonClick(OnButtonClick);
    ButtonClicked(); // Execute the function.
}  

À partir de là, nous pouvons passer aux expressions lambda et voir comment elles pourraient être utiles dans votre situation:
il existe de nombreux délégués déjà définis par les bibliothèques .NET, certains comme Action, qui n'acceptent aucun paramètre et ne renvoient aucune valeur. Il est défini comme public delegate void Action();
Vous pouvez toujours l'utiliser selon vos besoins au lieu de définir un nouveau délégué à chaque fois. Dans le contexte précédent par exemple, vous auriez pu juste écrire

Action ButtonClicked = new Action(OnButtonClick);
ButtonClicked();

qui aurait fait de même.
Maintenant que vous avez vu différentes façons d'utiliser les délégués, utilisons notre première expression lambda. Les expressions Lambda sont des fonctions anonymes; donc, ce sont des fonctions normales mais sans nom. Ils sont de ces formes:

x => DoSomethingWithX(x);
(x) => DoSomethingWithX(x);
(x,y) => DoSometingWithXY(x,y);
() => Console.WriteLine("I do not have parameters!");

Dans notre cas, nous n'avons aucun paramètre donc nous utiliserons la dernière expression. Nous pouvons l'utiliser comme la fonction OnButtonClick, mais nous avons l'avantage de ne pas avoir de fonction nommée. Nous pouvons à la place faire quelque chose comme ceci:

Action ButtonClicked = new Action( () => MessageBox.Show("Hello World!") );

ou encore plus facile,

Action ButtonClicked = () => MessageBox.Show("Hello World!");

alors appelez simplement ButtonClicked();Bien sûr, vous pouvez également avoir plusieurs lignes de code, mais je ne veux pas vous embrouiller davantage. Cela ressemblerait à ceci:

Action ButtonClicked = () => 
{
    MessageBox.Show("Hello World!");
};
ButtonClicked();

Vous pouvez également jouer, par exemple, vous pouvez exécuter une fonction comme celle-ci:

new Action(() => MessageBox.Show("Hello World!"))();

Désolé pour le long post, j'espère que ce n'était pas trop déroutant :)

EDIT: J'ai oublié de mentionner qu'une forme alternative qui, même si elle n'est pas souvent utilisée, pourrait rendre les expressions lambda plus faciles à comprendre:

new Action(delegate() {
    Console.WriteLine("I am parameterless");
})();

Aussi, en utilisant des génériques:

// Defines a delegate that has one parameter of type string. You could pass as many parameters as you want.
new Action<string>(delegate(string x) {
    Console.WriteLine(x);
})("I am a string parameter!");

À son tour, vous pouvez utiliser des expressions lambda, mais vous n'avez pas besoin (mais vous pourriez dans certains cas) définir le type du paramètre, par exemple, le code ci-dessus pourrait simplement être écrit comme suit:

new Action<string>(x => {
    Console.WriteLine(x);
})("I am a string parameter!");

ou:

new Action<string>(x => Console.WriteLine(x))("I am a string parameter!");

EDIT2:
Action<string>est une représentation de public void delegate Action(string obj);
Action<string,string>est une représentation de public void delegate Action(string obj, string obj2);
En général, Action<T>est une représentation depublic void delegate Action<T>(T obj);

EDIT3: Je sais que le message est ici depuis un moment, mais je pense que c'est vraiment cool de ne pas le mentionner: vous pouvez le faire, ce qui est principalement lié à votre question:

dynamic aFunction = (Func<string, DialogResult>)MessageBox.Show;
aFunction("Hello, world!");

ou simplement:

Func<string, DialogResult> aFunction = MessageBox.Show;
aFunction("Hello, world!");
user3439065
la source
7

La Lazyclasse est spécifiquement conçue pour représenter une valeur qui ne sera pas calculée tant que vous ne l'aurez pas demandée. Vous la construisez en fournissant une méthode qui définit comment elle doit être construite, mais elle gérera l'exécution de cette méthode pas plus d'une fois (même face à plusieurs threads demandant la valeur) et renvoyant simplement la valeur déjà construite pour toute demande supplémentaire:

var foo = new Lazy<DialogResult>(()=>MessageBox.Show("Hello, World!"));

var result = foo.Value;
Servy
la source
Rappelez-vous que cela Lazydoit être utilisé pour les valeurs qui nécessitent beaucoup de puissance de traitement, et que vous ne devez pas les utiliser pour l'interaction (car la sémantique de .Valueest qu'elle renvoie une valeur, similaire à une propriété, pas une action (interactive)). Un délégué doit être utilisé pour de telles actions à la place.
Abel
1
@Abel Non, ce n'est pas pour les valeurs qui nécessitent beaucoup de puissance de traitement, c'est pour toute valeur dont vous souhaitez différer l'initialisation jusqu'à ce qu'elle soit demandée, sans jamais initialiser cette valeur plus d'une fois. Ici, la valeur de Value est utilisée; c'est le DialogResultreçu de l'affichage de la boîte de message. La principale différence entre cette solution et l'utilisation d'un délégué est de savoir si la valeur doit être recalculée à chaque fois qu'elle est demandée ou non. Mon interprétation des exigences était que cela initialise conceptuellement une valeur, pas une opération à répéter.
Servy
Lazypeut facilement être mal utilisé. Il a une surcharge en soi, l'utiliser "juste" pour différer une petite tâche entraînera plus de surcharge qu'il n'en gagne. Afficher les boîtes de message d'une propriété est une mauvaise pratique (imo) en général, indépendamment de Lazy. Btw, de MSDN, je cite: "Utilisez l'initialisation paresseuse pour différer la création d'un objet volumineux ou gourmand en ressources" . Vous pouvez être en désaccord avec cela, mais c'est pour cela qu'il a été conçu à l'origine.
Abel
1
@Abel La surcharge de performance Lazydans un contexte comme celui-ci est certainement négligeable; cela pâlira par rapport au temps passé à attendre qu'un humain clique sur une boîte de message. Cela se résume principalement aux exigences réelles de l'application sous-jacente; le caractère vague de la question rend impossible une réponse objectivement correcte. Ceci est une interprétation de la question. Quant à faire beaucoup de travail dans une propriété getter étant mauvais; apparemment, vous êtes fondamentalement opposé à la conception entière de Lazy. Vous êtes les bienvenus à cette opinion.
Servy
Désolé, vous devez m'avoir mal compris. Certes, MessageBox la surcharge est négligeable (je n'utiliserais tout simplement pas l'interface utilisateur à l'intérieur d'une propriété). Je voulais dire de petites tâches en général (comme le report 2 + 3 * 4 / i), où la surcharge de création de la fermeture est plus grande que le calcul lui-même. Et je pense que j'adhère pleinement Lazy, en fait nous l'utilisons beaucoup en F # (un peu moins en C #) et nous avons appris à la dure qu'il faut faire attention, en particulier. dans le respect de la performance.
Abel
4

La façon dont je lis votre question, c'est dans le contexte des contrôles GUI?

S'il s'agit de WPF, jetez un œil à la "bonne" façon de gérer les commandes des contrôles: http://msdn.microsoft.com/en-us/library/ms752308(v=vs.110).aspx

... mais cela peut être pénible et exagéré. Pour un cas général plus simple, vous recherchez peut-être un gestionnaire d'événements, comme:

myButton.Click += (o, e) => MessageBox.Show("Hello, World!");

Ce gestionnaire d'événements peut être géré de différentes manières. L'exemple ci-dessus utilise une fonction anonyme, mais vous pouvez également faire:

Action<object, RoutedEventArgs> sayHello = (o, e) => MessageBox.Show("Hello, World");
myButton.Click += new RoutedEventHandler(sayHello);

... comme vous le demandiez, avec une fonction (ou ici, "Action", car elle renvoie void) assignée comme variable.

Zaccone
la source
1

Vous pouvez affecter du code C # à une variable, le compiler au moment de l'exécution et exécuter le code:

  • Écrivez votre code:

    // Assign C# code to the code variable.
    string code = @"
    using System;
    
    namespace First
    {
        public class Program
        {
            public static void Main()
            {
                " +
                "Console.WriteLine(\"Hello, world!\");"
                + @"
            }
        }
    }
    ";
  • Créez le fournisseur et les paramètres du compilateur:

    CSharpCodeProvider provider = new CSharpCodeProvider();
    CompilerParameters parameters = new CompilerParameters();
  • Définissez les paramètres du compilateur:

    // Reference to System.Drawing library
    parameters.ReferencedAssemblies.Add("System.Drawing.dll");
    // True - memory generation, false - external file generation
    parameters.GenerateInMemory = true;
    // True - exe file generation, false - dll file generation
    parameters.GenerateExecutable = true;
  • Compiler l'assemblage:

    CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
  • Vérifier les erreurs:

    if (results.Errors.HasErrors)
    {
            StringBuilder sb = new StringBuilder();
    
            foreach (CompilerError error in results.Errors)
            {
                    sb.AppendLine(String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText));
            }
    
            throw new InvalidOperationException(sb.ToString());
    }
  • Obtenez l'assembly, le type et la méthode Main:

    Assembly assembly = results.CompiledAssembly;
    Type program = assembly.GetType("First.Program");
    MethodInfo main = program.GetMethod("Main");
  • Exécuter:

    main.Invoke(null, null);

Référence:

http://www.codeproject.com/Tips/715891/Compiling-Csharp-Code-at-Runtime

Amir Saniyan
la source
Je ne pense pas que la compilation de code dynamique soit du tout pertinente par rapport à la question posée.
Iravanchi