it-swarm.com.de

Warum muss ein Lambda-Ausdruck umgewandelt werden, wenn er als einfacher Delegate-Parameter angegeben wird?

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

Warum führt dies zu einem Fehler bei der Kompilierung:

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

Dies funktioniert jedoch gut:

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

Wann erwartet die Methode einen einfachen Delegaten?

120
xyz

Ein Lambda-Ausdruck kann entweder in einen Delegattyp oder einen Ausdrucksbaum konvertiert werden - er muss jedoch wissen, welcher Delegattyp. Es reicht nicht aus, nur die Signatur zu kennen. Angenommen, ich habe:

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

...

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

Was würden Sie von dem konkreten Typ des Objekts erwarten, auf das x verweist? Ja, der Compiler könnte einen neuen Delegattyp mit einer entsprechenden Signatur generieren, aber das ist selten nützlich und Sie haben weniger Gelegenheit zur Fehlerprüfung.

Wenn Sie es einfach machen möchten, Control.Invoke Mit einem Action fügen Sie am einfachsten eine Erweiterungsmethode zu Control hinzu:

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

Sind Sie es leid, immer wieder Lambdas zu 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());
    }
}
33
Andrey Naumov

Neun Zehntel der Zeit bekommen die Leute das, weil sie versuchen, auf dem UI-Thread zu marshallen. Hier ist der faule Weg:

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

Jetzt, da es eingegeben ist, verschwindet das Problem (siehe Antwort von Skeet) 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 noch ein Tipp. Sie würden dies nicht für UI-Sachen tun, aber in Fällen, in denen Sie SomeMethod zum Blockieren benötigen, bis es abgeschlossen ist (z. B. Anforderungs-/Antwort-E/A, Warten auf die Antwort), verwenden Sie ein WaitHandle (siehe msdn WaitAll, WaitAny, WaitOne).

Beachten Sie, dass AutoResetEvent ein WaitHandle-Derivat ist.

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 noch ein letzter Tipp, weil sich die Dinge verheddern können: WaitHandles blockieren den Faden. Das ist, was sie tun sollen. Wenn Sie versuchen, auf dem UI-Thread zu marshallen, während Sie ihn blockiert haben , bleibt Ihre App hängen. In diesem Fall ist (a) ernsthaftes Refactoring angebracht, und (b) als vorübergehender Hack können Sie wie folgt warten:

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

Peter Wone. Du bist ein Mann. Ich habe Ihr Konzept weiterentwickelt und mir diese beiden Funktionen ausgedacht.

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

Ich füge diese beiden Funktionen in meine Formular-App ein und kann so Anrufe von Mitarbeitern im Hintergrund tätigen

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 muss keine vom Arbeiter ausgeführten Funktionen einrichten, was in solchen Fällen sehr praktisch ist

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

Holen Sie sich im Wesentlichen einige IP-Adressen aus einem GUI-DataGridView, pingen Sie sie, setzen Sie die resultierenden Symbole auf grün oder rot und aktivieren Sie die Schaltflächen im Formular erneut. Ja, es ist ein "parallel.for" in einem Backgroundworker. Ja, es ruft viel Overhead auf, ist aber für kurze Listen und viel kompakteren Code vernachlässigbar.

4
rocketsarefast

Ich habe versucht, auf der Antwort von @ Andrey Naumov aufzubauen. Möglicherweise ist dies eine leichte Verbesserung.

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

Wobei Typparameter S der formale Parameter ist (der Eingabeparameter, der mindestens erforderlich ist, um den Rest der Typen abzuleiten). Jetzt kannst du es so nennen:

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 zusätzliche Überladungen für Action<S> Und Expression<Action<S>> In derselben Klasse haben. Für other eingebaute Delegate- und Ausdruckstypen müssen Sie separate Klassen wie Lambda, Lambda<S, T>, Lambda<S, T, U> Usw. schreiben.

Vorteil davon sehe ich gegenüber dem ursprünglichen Ansatz:

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

  2. Das gibt Ihnen die Freiheit, es gegen jeden Func<int, T> Zu verwenden, nicht nur, wenn T sagt, string, wie in den Beispielen gezeigt.

  3. Unterstützt sofort Ausdrücke. In der früheren Vorgehensweise müssen Sie die Typen erneut angeben, wie z.

    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. Das Erweitern der Klasse für andere Delegat- (und Ausdrucks-) Typen ist ähnlich umständlich 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 nur einmal deklarieren (das zu eins weniger für Funcs).


Eine andere Möglichkeit, die Antwort von Andrey umzusetzen, ist, nicht vollständig generisch zu werden

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 reduzieren sich die Dinge auf:

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

Das ist noch weniger tippen, aber Sie verlieren bestimmte Typensicherheit, und imo, das ist es nicht wert.

1
nawfal

Ein bisschen zu spät zur Party, aber Sie können auch so besetzen

this.BeginInvoke((Action)delegate {
    // do awesome stuff
});
1
Tien Dinh

Durch das Spielen mit XUnit und Fluent Assertions konnte diese Inline-Funktion auf eine Weise eingesetzt werden, die ich wirklich cool finde.

Vorher

[Fact]
public void Pass_Open_Connection_Without_Provider()
{
    Action action = () => {
        using (var c = DbProviderFactories.GetFactory("MySql.Data.MySqlClient").CreateConnection())
        {
            c.ConnectionString = "<xxx>";
            c.Open();
        }
    };

    action.Should().Throw<Exception>().WithMessage("xxx");
}

Nach

[Fact]
public void Pass_Open_Connection_Without_Provider()
{
    ((Action)(() => {
        using (var c = DbProviderFactories.GetFactory("<provider>").CreateConnection())
        {
            c.ConnectionString = "<connection>";
            c.Open();
        }
    })).Should().Throw<Exception>().WithMessage("Unable to find the requested .Net Framework Data Provider.  It may not be installed.");
}
 this.Dispatcher.Invoke((Action)(() => { textBox1.Text = "Test 123"; }));
0
Narottam Goyal