¿Por qué se debe convertir una expresión lambda cuando se proporciona como un parámetro de delegado simple?

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

  •  03-07-2019
  •  | 
  •  

Pregunta

Tome el método System.Windows.Forms.Control.Invoke (método delegado)

¿Por qué esto da un error en tiempo de compilación?

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

Sin embargo, esto funciona bien:

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

¿Cuándo el método espera un Delegado simple?

¿Fue útil?

Solución

Una expresión lambda puede convertirse a un tipo de delegado o un árbol de expresión, pero tiene que saber qué tipo de delegado. Solo saber que la firma no es suficiente. Por ejemplo, supongamos que tengo:

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

...

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

¿Cuál esperarías que sea el tipo concreto del objeto al que se refiere x? Sí, el compilador podría generar un nuevo tipo de delegado con una firma apropiada, pero eso rara vez es útil y terminas con menos oportunidades para la verificación de errores.

Si desea facilitar la llamada a Control.Invoke con un Action lo más fácil es agregar un método de extensión a Control:

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

Otros consejos

¿Cansado de lanzar lambdas una y otra vez?

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

Nueve décimas de las veces que las personas obtienen esto porque están tratando de reunir el hilo de la interfaz de usuario. Aquí está la manera perezosa:

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

Ahora que está escrito, el problema desaparece (qv Skeet's anwer) y tenemos esta sintaxis muy sucinta:

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

Para puntos de bonificación aquí hay otro consejo. No haría esto para las cosas de la interfaz de usuario, pero en los casos en que necesite SomeMethod para bloquear hasta que se complete (por ejemplo, E / S de solicitud / respuesta, esperando la respuesta) use un WaitHandle (qv msdn WaitAll, WaitAny, WaitOne).

Tenga en cuenta que AutoResetEvent es un derivado de 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
}

Y un consejo final porque las cosas pueden enredarse: WaitHandles detiene el hilo. Esto es lo que se supone que deben hacer. Si intentas reunir el hilo de la interfaz de usuario mientras lo tienes bloqueado , tu aplicación se bloqueará. En este caso (a) es necesaria una refactorización seria, y (b) como un truco temporal puede esperar así:

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

Peter Wone. eres un hombre Llevando su concepto un poco más allá, se me ocurrieron estas dos funciones.

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

Coloco estas dos funciones en mi aplicación Form, y puedo hacer llamadas de trabajadores en segundo plano como esta

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

Tal vez un poco vago, pero no tengo que configurar las funciones realizadas por el trabajador, lo cual es muy útil en casos como este

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

Básicamente, obtenga algunas direcciones IP de un DataGridView gui, haga ping, configure los iconos resultantes en verde o rojo y vuelva a habilitar los botones en el formulario. Sí, es un & "; Paralelo. Para &"; en un trabajador de fondo. Sí, es MUCHA invocación de gastos generales, pero es insignificante para listas cortas y código mucho más compacto.

Intenté construir esto sobre @Andrey NaumovLa respuesta.Quizás esto sea una ligera mejora.

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

Donde tipo parámetro S es el parámetro formal (el parámetro de entrada, que es el mínimo requerido para inferir el resto de los tipos).Ahora puedes llamarlo así:

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

Puede tener sobrecargas adicionales para Action<S> y Expression<Action<S>> de manera similar en la misma clase.Para otro integrados en tipos de delegados y expresiones, tendrás que escribir clases separadas como Lambda, Lambda<S, T>, Lambda<S, T, U> etc.

Veo la ventaja de esto sobre el enfoque original:

  1. Una especificación de tipo menos (solo es necesario especificar el parámetro formal).

  2. Lo que te da la libertad de usarlo contra cualquier Func<int, T>, no sólo cuando T es decir, string, como se muestra en los ejemplos.

  3. Admite expresiones de inmediato.En el método anterior tendrás que volver a especificar tipos, como:

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

    para expresiones.

  4. Extender la clase para otros tipos de delegados (y expresiones) es igualmente engorroso como lo anterior.

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

En mi enfoque, tienes que declarar tipos solo una vez (también una menos para Funcs).


Otra forma de implementar la respuesta de Andrey es no volverse completamente genérico.

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

Entonces las cosas se reducen a:

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

Eso es incluso menos escritura, pero se pierde cierta seguridad de escritura, y En mi opinión, esto no vale la pena.

Un poco tarde para la fiesta, pero también puedes emitir así

this.BeginInvoke((Action)delegate {
    // do awesome stuff
});
 this.Dispatcher.Invoke((Action)(() => { textBox1.Text = "Test 123"; }));
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top