Warum ein Lambda-Ausdruck muss gegossen werden, wenn sie als Klar Delegate Parameter geliefert

StackOverflow https://stackoverflow.com/questions/411579

  •  03-07-2019
  •  | 
  •  

Frage

Nehmen Sie die Methode System.Windows.Forms.Control.Invoke (Delegate-Methode)

Warum geben bedeutet dies eine Kompilierung-Fehler:

string str = "woop";
Invoke(() => this.Text = str);
// Error: Cannot convert lambda expression to type 'System.Delegate'
// because it is not a delegate type

Doch das funktioniert gut:

string str = "woop";
Invoke((Action)(() => this.Text = str));

Wenn die Methode einen einfachen Delegierten erwartet?

War es hilfreich?

Lösung

Ein Lambda-Ausdruck kann entweder auf einen Delegattyp umgewandelt werden oder ein Ausdruck Baum - aber es muss wissen, die Delegattyp. Nur die Unterschrift zu wissen, ist nicht genug. Zum Beispiel: Angenommen ich habe:

public delegate void Action1();
public delegate void Action2();

...

Delegate x = () => Console.WriteLine("hi");

Was werden Sie die konkrete Art des durch x genannte Objekt erwarten zu sein? Ja, der Compiler könnte erzeugt einen neuen Delegattyp mit einer entsprechenden Signatur, aber das ist selten sinnvoll und Sie mit weniger Gelegenheit am Ende für die Fehlerprüfung.

Wenn Sie wollen, um es einfach die einfachste Sache Control.Invoke mit einem Action zu nennen ist zu tun, eine Erweiterungsmethode hinzufügen Steuerung:

public static void Invoke(this Control control, Action action)
{
    control.Invoke((Delegate) action);
}

Andere Tipps

Müde von lambdas über und über Gießen?

public sealed class Lambda<T>
{
    public static Func<T, T> Cast = x => x;
}

public class Example
{
    public void Run()
    {
        // Declare
        var c = Lambda<Func<int, string>>.Cast;
        // Use
        var f1 = c(x => x.ToString());
        var f2 = c(x => "Hello!");
        var f3 = c(x => (x + x).ToString());
    }
}

Neun Zehntel der Zeit, die Menschen bekommen, weil sie auf den UI-Thread zu ordnen versuchen. Hier ist die faule Art und Weise:

static void UI(Action action) 
{ 
  System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(action); 
}

Nun, da er eingegeben hat, das Problem verschwindet (siehe dort Skeet anwer), und wir haben diese sehr prägnante Syntax:

int foo = 5;
public void SomeMethod()
{
  var bar = "a string";
  UI(() =>
  {
    //lifting is marvellous, anything in scope where the lambda
    //expression is defined is available to the asynch code
    someTextBlock.Text = string.Format("{0} = {1}", foo, bar);        
  });
}

Für Bonuspunkte hier ist ein weiterer Tipp. Sie würden dies für UI-Sachen nicht tun, aber in Fällen, in denen Sie müssen Somemethod zu blockieren, bis es abgeschlossen ist (zB Request / Response-I / O, für die Antwort warten) verwenden, um ein Waithandle (qv Msdn WaitAll, WaitAny, WaitOne).

Beachten Sie, dass Autoreset ist ein Waithandle-Derivat.

public void BlockingMethod()
{
  AutoResetEvent are = new AutoResetEvent(false);
  ThreadPool.QueueUserWorkItem ((state) =>
  {
    //do asynch stuff        
    are.Set();
  });      
  are.WaitOne(); //don't exit till asynch stuff finishes
}

Und ein letzter Tipp, weil die Dinge können verheddern: waithandles Stall den Faden. Das ist, was sie tun soll. Wenn Sie versuchen, auf den UI-Thread Marschall , während Sie haben es ins Stocken geraten , wird Ihre App hängen. In diesem Fall (a) einige ernsthafte Refactoring ist in Ordnung, und (b) als temporäre Hack Sie so warten kann:

  bool wait = true;
  ThreadPool.QueueUserWorkItem ((state) =>
  {
    //do asynch stuff        
    wait = false;
  });
  while (wait) Thread.Sleep(100);

Peter Wone. Sie sind da Menschen. Unter Ihrem Konzept ein bisschen weiter, kam ich mit diesen beiden Funktionen auf.

private void UIA(Action action) {this.Invoke(action);}
private T UIF<T>(Func<T> func) {return (T)this.Invoke(func);}

Ich stelle diese beiden Funktionen in mein Formular app, und ich kann Anrufe von Hintergrund Arbeiter wie diese machen

int row = 5;
string ip = UIF<string>(() => this.GetIp(row));
bool r = GoPingIt(ip);
UIA(() => this.SetPing(i, r));

Vielleicht ein bisschen faul, aber ich habe keine Arbeiter getan Funktionen einrichten, die in super handlich kommt in Fällen wie dem vorliegenden

private void Ping_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
  int count = this.dg.Rows.Count;
  System.Threading.Tasks.Parallel.For(0, count, i => 
  {
    string ip = UIF<string>(() => this.GetIp(i));
    bool r = GoPingIt(ip);
    UIA(() => this.SetPing(i, r));
  });
  UIA(() => SetAllControlsEnabled(true));
}

Im Wesentlichen einige IP-Adressen aus einem gui Datagridview erhalten, ping sie, stellen die resultierenden Symbole grün oder rot, und reaktivieren Sie Tasten auf dem Formular. Ja, es ist ein „Parallel.For“ in einem Background. Ja, es gibt eine Menge Aufwand für den Aufruf, aber seine vernachlässigbar für kurze Listen und viel kompakter Code.

Ich habe versucht, dies auf @Andrey Naumov 's Antwort zu bauen. Vielleicht ist dies eine leichte Verbesserung ist.

public sealed class Lambda<S>
{
    public static Func<S, T> CreateFunc<T>(Func<S, T> func)
    {
        return func;
    }

    public static Expression<Func<S, T>> CreateExpression<T>(Expression<Func<S, T>> expression)
    {
        return expression;
    }

    public Func<S, T> Func<T>(Func<S, T> func)
    {
        return func;
    }

    public Expression<Func<S, T>> Expression<T>(Expression<Func<S, T>> expression)
    {
        return expression;
    }
}

Bei Typ-Parameter S ist der formale Parameter (der Eingangsparameter, die mindestens zu schließen Rest der Typen erforderlich ist). Jetzt können Sie es nennen wie:

var l = new Lambda<int>();
var d1 = l.Func(x => x.ToString());
var e1 = l.Expression(x => "Hello!");
var d2 = l.Func(x => x + x);

//or if you have only one lambda, consider a static overload
var e2 = Lambda<int>.CreateExpression(x => "Hello!");

Sie können haben zusätzliche Überlastungen für Action<S> und in ähnlicher Weise in der gleichen Klasse Expression<Action<S>>. Für andere in Delegaten und Ausdrucksarten gebaut, werden Sie separate Klassen wie Lambda, Lambda<S, T>, Lambda<S, T, U> usw. schreiben.

Vorteil dieses sehe ich über dem ursprünglichen Ansatz:

  1. Eine weniger Typangabe (nur der formale Parameter muss angegeben werden).

  2. Was gibt Ihnen die Freiheit es gegen jede Func<int, T> zu verwenden, nicht nur, wenn T sagen ist, string, wie in den Beispielen gezeigt.

  3. Unterstützt Ausdrücke sofort. In dem früheren Ansatz werden Sie Typen wieder angeben, wie:

    var e = Lambda<Expression<Func<int, string>>>.Cast(x => "Hello!");
    
    //or in case 'Cast' is an instance member on non-generic 'Lambda' class:
    var e = lambda.Cast<Expression<Func<int, string>>>(x => "Hello!");
    

    für Ausdrücke.

  4. Erweiterung der Klasse für andere delegieren (und Ausdruck) Typen ähnlich umständlich ist wie oben.

    var e = Lambda<Action<int>>.Cast(x => x.ToString());
    
    //or for Expression<Action<T>> if 'Cast' is an instance member on non-generic 'Lambda' class:
    var e = lambda.Cast<Expression<Action<int>>>(x => x.ToString());
    

In meinem Ansatz müssen Sie Typen deklarieren nur einmal (das auch ein weniger für Funcs).


Eine weitere Möglichkeit, Andrey Antwort zu implementieren ist wie nicht vollständig generic gehen

public sealed class Lambda<T>
{
    public static Func<Func<T, object>, Func<T, object>> Func = x => x;
    public static Func<Expression<Func<T, object>>, Expression<Func<T, object>>> Expression = x => x;
}

So Dinge reduzieren zu:

var l = Lambda<int>.Expression;
var e1 = l(x => x.ToString());
var e2 = l(x => "Hello!");
var e3 = l(x => x + x);

Das ist sogar weniger tippen, aber Sie bestimmte Art Sicherheit verlieren, und imo, das ist es nicht wert.

Bit spät zur Party, aber Sie können auch so werfen

this.BeginInvoke((Action)delegate {
    // do awesome stuff
});
 this.Dispatcher.Invoke((Action)(() => { textBox1.Text = "Test 123"; }));
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top