Waarom moet een lambda-expressie worden gecast als deze wordt opgegeven als een gewone Delegate-parameter

Neem de methode System.Windows.Forms.Control.Invoke(Delegate methode)

Waarom geeft dit een compilatiefout:

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

Toch werkt dit prima:

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

Wanneer verwacht de methode een gewone afgevaardigde?


Antwoord 1, autoriteit 100%

Een lambda-expressie kan ofwel worden geconverteerd naar een gedelegeerd type of een expressiestructuur – maar het moet weten welkgedelegeerd type. Alleen de handtekening kennen is niet genoeg. Stel dat ik bijvoorbeeld het volgende heb:

public delegate void Action1();
public delegate void Action2();
...
Delegate x = () => Console.WriteLine("hi");

Wat zou je verwachten van het concrete type van het object waarnaar wordt verwezen door x? Ja, de compiler zoueen nieuw type gemachtigde kunnen genereren met een geschikte handtekening, maar dat is zelden nuttig en u krijgt uiteindelijk minder kans op foutcontrole.

Als u het gemakkelijk wilt maken om Control.Invokeaan te roepen met een Action, kunt u het eenvoudigst een extensiemethode toevoegen aan Control:

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

Antwoord 2, autoriteit 27%

Ben je het zat om steeds opnieuw lambda’s te gieten?

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

Antwoord 3, autoriteit 10%

Negen tienden van de tijd krijgen mensen dit omdat ze proberen de UI-thread te bereiken. Dit is de luie manier:

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

Nu het is getypt, is het probleem verdwenen (zie het antwoord van Skeet) en hebben we deze zeer beknopte syntaxis:

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

Voor bonuspunten is hier nog een tip. Je zou dit niet doen voor UI-dingen, maar in gevallen waarin je SomeMethod nodig hebt om te blokkeren totdat het is voltooid (bijvoorbeeld verzoek/antwoord-I/O, wachtend op het antwoord), gebruik dan een WaitHandle(zie msdn WaitAll, WaitAny, WaitOne).

Merk op dat AutoResetEvent een WaitHandle-derivaat is.

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
}

En een laatste tip omdat dingen in de knoop kunnen raken: WaitHandles houdt de draad vast. Dit is wat ze zouden moeten doen. Als u probeert de UI-thread te bereiken terwijl u deze hebt vastgelopen, loopt uw ​​app vast. In dit geval (a) is een serieuze refactoring op zijn plaats, en (b) als tijdelijke hack kun je als volgt wachten:

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

Antwoord 4, autoriteit 3%

Peter Wone. jij bent de man.
Ik ging wat verder met je concept en bedacht deze twee functies.

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

Ik plaats deze twee functies in mijn Formulier-app en ik kan op deze manier bellen met achtergrondwerkers

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

Misschien een beetje lui, maar ik hoef geen door werknemers uitgevoerde functies in te stellen,
wat erg handig is in gevallen als deze

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

Krijg in wezen een aantal ip-adressen van een gui DataGridView, ping ze, stel de resulterende pictogrammen in op groen of rood en schakel de knoppen op het formulier opnieuw in. Ja, het is een “parallel.for” in een backgroundworker. Ja, het is VEEL overhead, maar het is verwaarloosbaar voor korte lijsten en veel compactere code.


Antwoord 5

Ik heb geprobeerd dit te bouwen op basis van het antwoord van @Andrey Naumov. Misschien is dit een kleine verbetering.

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

Waarbij typeparameter Sde formele parameter is (de invoerparameter, die minimaal vereist is om de rest van de typen af ​​te leiden). Nu kun je het zo noemen:

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

U kunt op dezelfde manier extra overbelastingen hebben voor Action<S>en Expression<Action<S>>. Voor andereingebouwde typen gedelegeerde en expressies, moet u afzonderlijke klassen schrijven, zoals Lambda, Lambda<S, T>, Lambda<S, T, U>enz.

Voordeel hiervan zie ik boven de oorspronkelijke aanpak:

  1. Eén typespecificatie minder (alleen de formele parameter hoeft te worden gespecificeerd).

  2. Dat geeft je de vrijheid om het te gebruiken tegen elke Func<int, T>, niet alleen wanneer Tis, zeg, string, zoals getoond in voorbeelden.

  3. Ondersteunt uitdrukkingen meteen. In de eerdere benadering moet u typen opnieuw specificeren, zoals:

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

    voor uitdrukkingen.

  4. Het uitbreiden van de klas voor andere typen gedelegeerden (en expressies) is net zo omslachtig als hierboven.

    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 mijn benadering hoef je typen maar één keer te declareren (ook die ene minder voor Funcs).


Een andere manier om het antwoord van Andrey te implementeren is alsof je niet volledig generiek gaat

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

Dus dingen worden teruggebracht tot:

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

Dat is nog minder typen, maar je verliest bepaalde typeveiligheid, en imo, dit is het niet waard.


Antwoord 6

Beetje laat voor het feest, maar je kunt ook zo casten

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

Antwoord 7

this.Dispatcher.Invoke((Action)(() => { textBox1.Text = "Test 123"; }));

Antwoord 8

Spelen met XUnit en Fluent Assertionswas het mogelijk om deze inline-mogelijkheid te gebruiken op een manier die ik echt cool vind .

Vóór

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

Na

[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.");
}

Other episodes