Zuweisen von Code zu einer Variablen

124

Ist es möglich, eine Variable zu erstellen und ihr eine Codezeile zuzuweisen, z.

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

... wenn ich die Variable verwende, wird die Codezeile ausgeführt.

user3539891
quelle
100
+1 für die seltene Kombination, neu im Codieren zu sein und eine gute Frage zu stellen: Sie verstehen, was Sie tun möchten, und erklären es gut. Sie kennen den Begriff einfach nicht, sodass Sie ihn nicht selbst finden können.
Tim S.
10
Der Begriff, den Sie suchen, ist ein Delegierter .
Lasse V. Karlsen
stackoverflow.com/questions/6187944/… check this out, ich denke, es gibt genug Erklärungen, die du brauchst. Asp funktioniert in dieser Angelegenheit fast wie Winforms.
CSharpie
Klingt sehr nach Blöcken in Objective-c
Brian Tracy

Antworten:

89

Sie könnten es einem solchen zuordnen Action:

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

Dann nenne es:

ButtonClicked();

Der Vollständigkeit halber (in Bezug auf die verschiedenen Kommentare) ...

Wie Erik sagte, können Sie mehrere Codezeilen ausführen:

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

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

Wie Tim sagte, könnten Sie das ActionSchlüsselwort weglassen

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

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

Um auf KRyans Kommentar zu den leeren Klammern einzugehen, der die Liste der Parameter darstellt, die Sie an die Aktion senden möchten (in diesem Fall keine) .

Wenn Sie beispielsweise die anzuzeigende Nachricht angeben möchten, können Sie "message" als Parameter hinzufügen (beachten Sie, dass ich geändert Action habe , um einen einzelnen Zeichenfolgenparameter anzugeben) :Action<string>

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

ButtonClicked("hello world!");
vivat Fische
quelle
10
Action ButtonClicked = () => MessageBox.Show("hi");ist gleichwertig und IMO schöner (fügen Sie parens hinzu, wenn Sie bevorzugen)
Tim S.
1
Es ist auch möglich, dass die Aktion in mehr als eine einzelne Codezeile aufgelöst wird.
Erik Philips
2
@ CSharpie Ich bin mir nicht sicher, ob diese Annahme für das OP hilfreich ist.
Erik Philips
2
@CSharpie Warum konnte das OP dies nicht verwenden WinForms?
Vivat Fische
2
@ CSharpie Ich verstehe, was du sagst. Wenn er dies tatsächlich an ein Button.ClickEreignis anfügt und es nicht in einer Variablen speichert, die er zufällig benannt hat ButtonClicked.
Vivat Fische
51

In Ihrem Fall möchten Sie a verwenden delegate.

Mal sehen, wie ein Delegierter arbeitet und wie wir zu einer einfacheren Form gelangen können, indem wir sein Konzept verstehen:

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

Sie sehen, der Delegat hat die Form einer normalen Funktion, jedoch ohne Argumente (er kann wie jede andere Methode eine beliebige Anzahl von Argumenten annehmen, der Einfachheit halber jedoch nicht).

Verwenden wir jetzt das, was wir haben. Wir definieren den Delegaten genauso wie jede andere Variable:

ButtonClick ButtonClicked = new ButtonClick(OnButtonClick);

Wir haben im Grunde eine neue Variable namens ButtonClicked erstellt, die einen ButtonClick-Typ (der ein Delegat ist) hat und bei Verwendung die Methode in der OnButtonClick () -Methode ausführt.
Um es zu benutzen, rufen wir einfach an:ButtonClicked();

Der ganze Code wäre also:

delegate void ButtonClick();

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

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

Von hier aus können wir zu Lambda-Ausdrücken wechseln und sehen, wie sie in Ihrer Situation nützlich sein können:
Es gibt viele Delegierte, die bereits von .NET-Bibliotheken definiert wurden, einige davon wie Action, die keine Parameter akzeptieren und keinen Wert zurückgeben. Es ist definiert als public delegate void Action();
Sie können es jederzeit für Ihre Anforderungen verwenden, anstatt jedes Mal einen neuen Delegaten zu definieren. Im vorherigen Kontext hätten Sie zum Beispiel gerade schreiben können

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

das hätte das gleiche getan.
Nachdem Sie nun verschiedene Möglichkeiten zur Verwendung von Delegaten gesehen haben, verwenden wir unseren ersten Lambda-Ausdruck. Lambda-Ausdrücke sind anonyme Funktionen. Es handelt sich also um normale Funktionen, jedoch ohne Namen. Sie haben folgende Formen:

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

In unserem Fall haben wir keine Parameter, daher verwenden wir den letzten Ausdruck. Wir können dies genauso wie die OnButtonClick-Funktion verwenden, aber wir haben den Vorteil, dass wir keine benannte Funktion haben. Wir können stattdessen so etwas tun:

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

oder noch einfacher,

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

dann einfach anrufen ButtonClicked();Natürlich können Sie auch mehrere Codezeilen haben, aber ich möchte Sie nicht mehr verwirren. Es würde allerdings so aussehen:

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

Sie können auch herumspielen, zum Beispiel eine Funktion wie die folgende ausführen:

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

Entschuldigung für den langen Beitrag, hoffe es war nicht zu verwirrend :)

EDIT: Ich habe vergessen zu erwähnen, dass eine alternative Form, die, obwohl nicht oft verwendet, Lambda-Ausdrücke leichter verständlich machen könnte:

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

Verwenden von Generika:

// 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!");

Im Gegenzug könnten Sie Lambda-Ausdrücke verwenden, aber Sie müssen den Typ des Parameters nicht definieren (aber in einigen Fällen auch), zum Beispiel könnte der obige Code einfach wie folgt geschrieben werden:

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

oder:

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

EDIT2:
Action<string>ist eine Darstellung von public void delegate Action(string obj);
Action<string,string>ist eine Darstellung von public void delegate Action(string obj, string obj2);
Im Allgemeinen Action<T>ist eine Darstellung vonpublic void delegate Action<T>(T obj);

EDIT3: Ich weiß, dass der Beitrag schon eine Weile hier ist, aber ich finde das wirklich cool, um nicht zu erwähnen: Sie können dies tun, was hauptsächlich mit Ihrer Frage zusammenhängt:

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

oder einfach:

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

Die LazyKlasse wurde speziell entwickelt, um einen Wert darzustellen, der erst berechnet wird, wenn Sie danach fragen. Sie erstellen es, indem Sie eine Methode bereitstellen, die definiert, wie es erstellt werden soll. Diese Methode wird jedoch nur einmal ausgeführt (selbst wenn mehrere Threads den Wert anfordern) und einfach den bereits erstellten Wert für zusätzliche Anforderungen zurückgeben:

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

var result = foo.Value;
Servieren
quelle
Denken Sie daran, dass Lazydies für Werte verwendet werden sollte, die viel Rechenleistung erfordern, und dass Sie sie nicht für die Interaktion verwenden sollten (da die Semantik .Valuelautet, dass ein Wert zurückgegeben wird, der einer Eigenschaft ähnelt, keine (interaktive) Aktion). Für solche Aktionen sollte stattdessen ein Delegat verwendet werden.
Abel
1
@Abel Nein, es ist nicht für Werte gedacht , die viel Rechenleistung erfordern, sondern für Werte, deren Initialisierung Sie verschieben möchten, bis sie angefordert werden, ohne diesen Wert mehr als einmal zu initialisieren. Hier ist der Wert von Value wird verwendet; Es ist das DialogResultempfangene vom Anzeigen des Meldungsfelds. Der Hauptunterschied zwischen dieser Lösung und der Verwendung eines Delegaten besteht darin, ob der Wert bei jeder Anforderung neu berechnet werden soll oder nicht. Meine Interpretation der Anforderungen war, dass dies konzeptionell einen Wert initialisiert und keine zu wiederholende Operation .
Servy
Lazykann leicht falsch verwendet werden. Es hat Overhead von sich selbst, wenn es "nur" verwendet wird, um eine kleine Aufgabe aufzuschieben, wird mehr Overhead verursacht, als es gewinnt. Das Anzeigen von Nachrichtenfeldern aus einer Eigenschaft ist im Allgemeinen (imo) eine schlechte Praxis, unabhängig davon Lazy. Btw, von MSDN, ich zitiere: „lazy Initialisierung Mit der Schaffung einer großen oder ressourcenintensive Aufgabe verschieben“ . Sie können dem nicht zustimmen, aber dafür wurde es ursprünglich entwickelt.
Abel
1
@Abel Der Leistungsaufwand Lazyin einem solchen Kontext ist sicherlich vernachlässigbar. Es wird blass im Vergleich zu der Zeit, die darauf gewartet wird, dass ein Mensch auf ein Meldungsfeld klickt. Es kommt hauptsächlich auf die tatsächlichen Anforderungen der zugrunde liegenden Anwendung an. Die Unbestimmtheit der Frage macht eine objektiv korrekte Antwort unmöglich. Dies ist eine Interpretation der Frage. Was die Arbeit an einem Immobilienmakler angeht, der schlecht ist; anscheinend bist du grundsätzlich gegen das gesamte design von Lazy. Sie sind zu dieser Meinung willkommen.
Servy
Entschuldigung, Sie müssen mich missverstanden haben. Sicherlich ist MessageBox der Overhead vernachlässigbar (ich würde die Benutzeroberfläche in einer Eigenschaft einfach nicht verwenden). Ich meinte im Allgemeinen kleine Aufgaben (wie das Verschieben 2 + 3 * 4 / i), bei denen der Aufwand für die Erstellung des Abschlusses größer ist als die Berechnung selbst. Und ich denke, ich bin voll und ganz davon überzeugt Lazy, dass wir es häufig in F # verwenden (etwas weniger in C #) und wir haben auf die harte Weise gelernt, dass man vorsichtig damit sein muss, insb. in Bezug auf die Leistung.
Abel
4

Wie ich Ihre Frage lese, steht dies im Zusammenhang mit GUI-Steuerelementen?

Wenn dies in WPF der Fall ist, sehen Sie sich die "richtige" Methode zum Behandeln von Befehlen aus Steuerelementen an: http://msdn.microsoft.com/en-us/library/ms752308(v=vs.110).aspx

... aber das kann ein Schmerz und ein Overkill sein. Für einen einfacheren allgemeinen Fall suchen Sie möglicherweise nach einem Ereignishandler wie:

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

Dieser Ereignishandler kann auf verschiedene Arten behandelt werden. Das obige Beispiel verwendet eine anonyme Funktion, aber Sie können auch Folgendes tun:

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

... genau wie Sie gefragt haben, mit einer Funktion (oder hier "Aktion", da sie void zurückgibt) als Variable zugewiesen.

Zaccone
quelle
1

Sie können einer Variablen C # -Code zuweisen, ihn zur Laufzeit kompilieren und den Code ausführen:

  • Schreiben Sie Ihren 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!\");"
                + @"
            }
        }
    }
    ";
  • Erstellen Sie den Provider und die Parameter des Compilers:

    CSharpCodeProvider provider = new CSharpCodeProvider();
    CompilerParameters parameters = new CompilerParameters();
  • Parameter des Compilers definieren:

    // 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;
  • Baugruppe kompilieren:

    CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
  • Fehler überprüfen:

    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());
    }
  • Holen Sie sich Assembly, Typ und die Hauptmethode:

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

    main.Invoke(null, null);

Referenz:

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

Amir Saniyan
quelle
Ich glaube nicht, dass die dynamische Codekompilierung für die gestellte Frage relevant war.
Iravanchi