Почему должно быть приведено лямбда-выражение при предоставлении в виде простого параметра Delegate
Вопрос
Возьмите метод System.Windows.Forms.Control.Invoke (метод Delegate)
Почему это приводит к ошибке времени компиляции:
string str = "woop";
Invoke(() => this.Text = str);
// Error: Cannot convert lambda expression to type 'System.Delegate'
// because it is not a delegate type
Тем не менее, это работает нормально:
string str = "woop";
Invoke((Action)(() => this.Text = str));
Когда метод ожидает простого делегата?
Решение
Лямбда-выражение может быть преобразовано либо в тип делегата, либо в дерево выражений, но оно должно знать , какой тип делегата. Просто зная подпись недостаточно. Например, предположим, у меня есть:
public delegate void Action1();
public delegate void Action2();
...
Delegate x = () => Console.WriteLine("hi");
Каким будет конкретный тип объекта, на который ссылается x
? Да, компилятор может сгенерировать новый тип делегата с соответствующей подписью, но это редко полезно, и у вас меньше возможностей для проверки ошибок.
Если вы хотите упростить вызов Control.Invoke
с помощью Action
, проще всего добавить метод расширения в Control:
public static void Invoke(this Control control, Action action)
{
control.Invoke((Delegate) action);
}
Другие советы
Устали от литья лямбд снова и снова?
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());
}
}
Девять десятых времени люди получают это, потому что пытаются маршалировать в поток пользовательского интерфейса. Вот ленивый способ:
static void UI(Action action)
{
System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(action);
}
Теперь, когда он напечатан, проблема исчезла (ответ на вопрос Скита), и у нас есть очень лаконичный синтаксис:
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);
});
}
Для бонусных баллов есть еще один совет. Вы не сделаете этого для пользовательского интерфейса, но в тех случаях, когда вам необходимо заблокировать SomeMethod до его завершения (например, запрос / ответ ввода-вывода, ожидание ответа), используйте WaitHandle (qv msdn WaitAll, WaitAny, WaitOne). Р>
Обратите внимание, что AutoResetEvent является производной 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
}
И последний совет, потому что все может запутаться: WaitHandles останавливает поток. Это то, что они должны делать. Если вы попытаетесь выполнить маршализацию потока пользовательского интерфейса , пока он заблокирован , ваше приложение будет зависать. В этом случае (а) необходим серьезный рефакторинг, и (б) в качестве временного взлома вы можете подождать вот так:
bool wait = true;
ThreadPool.QueueUserWorkItem ((state) =>
{
//do asynch stuff
wait = false;
});
while (wait) Thread.Sleep(100);
Питер Воне. ты да человек. Продолжая вашу концепцию, я разработал эти две функции.
private void UIA(Action action) {this.Invoke(action);}
private T UIF<T>(Func<T> func) {return (T)this.Invoke(func);}
Я помещаю эти две функции в свое приложение Form и могу звонить фоновым работникам, как это
int row = 5;
string ip = UIF<string>(() => this.GetIp(row));
bool r = GoPingIt(ip);
UIA(() => this.SetPing(i, r));
Может быть, немного ленив, но мне не нужно настраивать рабочие функции, что очень удобно в таких случаях, как этот
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));
}
По сути, получите некоторые ip-адреса из графического интерфейса DataGridView, пропингуйте их, установите получившиеся значки в зеленый или красный цвет и включите кнопки в форме. Да, это & Quot; parallel.for & Quot; в фоновом режиме. Да, это МНОГО вызова накладных расходов, но оно незначительно для коротких списков и гораздо более компактного кода. Р>
Я попытался построить это на основе ответа @ Андрея Наумова . Может быть, это небольшое улучшение.
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;
}
}
Где параметр типа S
- это формальный параметр (входной параметр, минимальный необходимый для определения остальных типов). Теперь вы можете назвать это как:
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!");
Вы можете иметь дополнительные перегрузки для Action<S>
и Expression<Action<S>>
аналогично в одном классе. Для других встроенных типов делегатов и выражений вам придется написать отдельные классы, такие как Lambda
, Lambda<S, T>
, Lambda<S, T, U>
и т. Д.
Преимущество этого я вижу по сравнению с оригинальным подходом:
<Ол>Еще одна спецификация типа (необходимо указать только формальный параметр). Р>
Это дает вам свободу использовать его против любого Func<int, T>
, а не только когда T
, скажем, string
, как показано в примерах.
Поддерживает выражения сразу. При более раннем подходе вам придется снова указывать типы, например:
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!");
для выражений.
Расширение класса для других типов делегатов (и выражений) также громоздко, как и выше.
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());
В моем подходе вы должны объявлять типы только один раз (это тоже на один меньше для Func
s).
Еще один способ реализовать ответ Андрея - это не быть полностью общим
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;
}
Так что все сводится к:
var l = Lambda<int>.Expression;
var e1 = l(x => x.ToString());
var e2 = l(x => "Hello!");
var e3 = l(x => x + x);
Это еще меньше печатает, но вы теряете определенную безопасность типов, и имхо, это того не стоит.
Немного опоздал на вечеринку, но вы также можете сыграть вот так
this.BeginInvoke((Action)delegate {
// do awesome stuff
});
this.Dispatcher.Invoke((Action)(() => { textBox1.Text = "Test 123"; }));