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

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

¿Por qué esto da un error de tiempo de comstackció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)); 

Cuando el método espera un simple delegado?

Una expresión lambda puede convertirse a un tipo delegado o a un árbol de expresiones, pero debe 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 fuera el tipo concreto del objeto referido por x ? Sí, el comstackdor podría generar un nuevo tipo de delegado con una firma apropiada, pero eso rara vez es útil y usted termina teniendo menos oportunidades de verificar errores.

Si desea que sea más fácil llamar a Control.Invoke con una 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); } 

¿Estás cansado de lanzar lambdas una y otra vez?

 public sealed class Lambda { public static Func Cast = x => x; } public class Example { public void Run() { // Declare var c = Lambda>.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 intentan llegar al 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á tipeado, el problema desaparece (como el de Skeet) y tenemos esta syntax 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 obtener puntos de bonificación, aquí hay otro consejo. No haría esto para cosas de UI, pero en los casos en que necesite SomeMethod para bloquear hasta que se complete (por ejemplo, solicitud / respuesta de E / S, 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 se pueden enredar: WaitHandles detiene el hilo. Esto es lo que se supone que deben hacer. Si intenta alinearse en el hilo de la interfaz de usuario mientras lo tiene estancado , su aplicación se bloqueará. En este caso (a) algunas refactorizaciones serias están en orden, y (b) como un hack temporal puedes esperar así:

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

Peter Wone. eres da man. Llevando su concepto un poco más allá, surgieron estas dos funciones.

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

Pongo estas dos funciones en mi aplicación Formulario, y puedo hacer llamadas de trabajadores de fondo como este

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

Tal vez un poco flojo, pero no tengo que configurar las funciones de trabajador hecho, que 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(() => this.GetIp(i)); bool r = GoPingIt(ip); UIA(() => this.SetPing(i, r)); }); UIA(() => SetAllControlsEnabled(true)); } 

Esencialmente, obtenga algunas direcciones IP de un gui DataGridView, haga ping, establezca los icons resultantes en verde o rojo, y vuelva a habilitar los botones en el formulario. Sí, es un “parallel.for” en un backgroundworker. Sí, es MUCHA invocación de sobrecarga, pero es insignificante para listas cortas y código mucho más compacto.

Traté de construir esto sobre la respuesta de @Andrey Naumov . Puede ser que esto sea una ligera mejora.

 public sealed class Lambda { public static Func CreateFunc(Func func) { return func; } public static Expression> CreateExpression(Expression> expression) { return expression; } public Func Func(Func func) { return func; } public Expression> Expression(Expression> expression) { return expression; } } 

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

 var l = new Lambda(); 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.CreateExpression(x => "Hello!"); 

Puede tener sobrecargas adicionales para Action y Expression> forma similar en la misma clase. Para otros tipos incorporados de delegates y expresiones, tendrá que escribir clases separadas como Lambda , Lambda , Lambda etc.

Ventaja de esto veo sobre el enfoque original:

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

  2. Lo que le da la libertad de usarlo contra cualquier Func , no solo cuando T es decir, string , como se muestra en los ejemplos.

  3. Admite expresiones de inmediato. En el enfoque anterior, deberá especificar tipos nuevamente, como:

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

    para expresiones

  4. La extensión de la clase para otros tipos de delegado (y expresión) es similarmente engorrosa como en el ejemplo anterior.

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

En mi enfoque, debes declarar tipos solo una vez (también uno menos para Func ).


Otra forma de implementar la respuesta de Andrey es como no ir completamente genérico

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

Entonces las cosas se reducen a:

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

Eso es incluso menos tipeo, pero pierdes cierto tipo de seguridad, y yo también, esto no vale la pena.

Un poco tarde para la fiesta, pero también puedes lanzar de esta manera

 this.BeginInvoke((Action)delegate { // do awesome stuff }); 
  this.Dispatcher.Invoke((Action)(() => { textBox1.Text = "Test 123"; }));