c# - sintaxis - ¿Por qué debe emitirse una expresión lambda cuando se proporciona como un parámetro delegado simple?
lambda sintaxis (7)
Tome el método System.Windows.Forms.Control.Invoke (método Delegate)
¿Por qué esto da un error de 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));
Cuando el método espera un simple delegado?
¿Estás 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 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 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 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<T>(Func<T> 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<string>(() => 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<string>(() => 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 iconos 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<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 el parámetro tipo S
es el parámetro formal (el parámetro de entrada, que es el mínimo requerido para deducir 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>>
forma similar en la misma clase. Para otros tipos incorporados de delegados y expresiones, tendrá que escribir clases separadas como Lambda
, Lambda<S, T>
, Lambda<S, T, U>
etc.
Ventaja de esto veo sobre el enfoque original:
Una especificación de tipo menos (solo se debe especificar el parámetro formal).
Lo que le da la libertad de usarlo contra cualquier
Func<int, T>
, no solo cuandoT
es decir,string
, como se muestra en los ejemplos.Admite expresiones de inmediato. En el enfoque anterior, deberá especificar tipos nuevamente, 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
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<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, 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<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 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
});
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 compilador 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);
}
this.Dispatcher.Invoke((Action)(() => { textBox1.Text = "Test 123"; }));