Perché è necessario eseguire il cast di un'espressione lambda quando viene fornita come semplice parametro Delegate

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

  •  03-07-2019
  •  | 
  •  

Domanda

Prendi il metodo System.Windows.Forms.Control.Invoke (metodo delegato)

Perché questo dà un errore di compilazione:

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

Eppure funziona bene:

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

Quando il metodo prevede un delegato semplice?

È stato utile?

Soluzione

Un'espressione lambda può essere convertita in un tipo delegato o in un albero delle espressioni, ma deve conoscere quale tipo delegato. Solo sapere che la firma non è abbastanza. Ad esempio, supponiamo che io abbia:

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

...

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

Cosa ti aspetteresti dal tipo concreto dell'oggetto a cui fa riferimento x? Sì, il compilatore potrebbe generare un nuovo tipo di delegato con una firma appropriata, ma questo è raramente utile e si finisce con meno opportunità di controllo degli errori.

Se vuoi semplificare la chiamata Control.Invoke con un Action la cosa più semplice da fare è aggiungere un metodo di estensione a Control:

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

Altri suggerimenti

Stanco di lanciare ripetutamente lambda?

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

Nove decimi di volte le persone lo ottengono perché stanno tentando di eseguire il marshalling sul thread dell'interfaccia utente. Ecco il modo pigro:

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

Ora che è stato digitato, il problema scompare (qv Skeet's anwer) e abbiamo questa sintassi molto sintetica:

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

Per i punti bonus ecco un altro suggerimento. Non lo faresti per le cose dell'interfaccia utente, ma nei casi in cui è necessario bloccare SomeMethod fino al completamento (ad es. I / O richiesta / risposta, in attesa di risposta) utilizzare un WaitHandle (qv msdn WaitAll, WaitAny, WaitOne).

Nota che AutoResetEvent è un derivato WaitHandle.

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
}

E un suggerimento finale perché le cose possono aggrovigliarsi: WaitHandles blocca il thread. Questo è ciò che dovrebbero fare. Se provi a eseguire il marshalling sul thread dell'interfaccia utente mentre lo hai bloccato , l'app si bloccherà. In questo caso (a) è necessario un serio refactoring e (b) come hack temporaneo puoi aspettare in questo modo:

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

Peter Wone. sei un uomo. Portando il tuo concetto un po 'oltre, ho ideato queste due funzioni.

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

Inserisco queste due funzioni nell'app Form e posso effettuare chiamate da operatori in background come questa

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

Forse un po 'pigro, ma non devo impostare le funzioni fatte dall'operatore, che è molto utile in casi come questo

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

In sostanza, ottieni alcuni indirizzi IP da una DataGridView gui, esegui il ping, imposta le icone risultanti su verde o rosso e riattiva i pulsanti sul modulo. Sì, è un & Quot; parallel.for & Quot; in un lavoratore in background. Sì, è MOLTO invocare l'overhead, ma è trascurabile per gli elenchi brevi e il codice molto più compatto.

Ho provato a basare questo sulla risposta di @Andrey Naumov . Può essere questo un leggero miglioramento.

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

Dove parametro parametro S è il parametro formale (il parametro di input, che è minimo richiesto per inferire il resto dei tipi). Ora puoi chiamarlo come:

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

Puoi avere sovraccarichi aggiuntivi per Action<S> e Expression<Action<S>> in modo simile nella stessa classe. Per altri tipi di delegato ed espressione incorporati, dovrai scrivere classi separate come Lambda, Lambda<S, T>, Lambda<S, T, U> ecc.

Vantaggio di ciò che vedo rispetto all'approccio originale:

  1. Specifica del tipo in meno (è necessario specificare solo il parametro formale).

  2. Che ti dà la libertà di usarlo contro qualsiasi Func<int, T>, non solo quando T si dice, string, come mostrato negli esempi.

  3. Supporta immediatamente le espressioni. Nell'approccio precedente dovrai specificare nuovamente i tipi, come:

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

    per le espressioni.

  4. L'estensione della classe per altri tipi delegati (ed espressioni) è altrettanto ingombrante come sopra.

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

Nel mio approccio devi dichiarare i tipi solo una volta (anche quello in meno per Func s).


Un altro modo per implementare la risposta di Andrey è come non diventare completamente generico

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

Quindi le cose si riducono a:

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

È ancora meno digitando, ma perdi una certa sicurezza del tipo e imo, questo non ne vale la pena.

Arriva in ritardo alla festa ma puoi anche lanciare in questo modo

this.BeginInvoke((Action)delegate {
    // do awesome stuff
});
 this.Dispatcher.Invoke((Action)(() => { textBox1.Text = "Test 123"; }));
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top