Почему лямбда-выражение должно быть приведено в качестве простого параметра делегата
возьмите метод системы.Окна.Формы.Управление.Вызов(метод делегата)
почему это дает ошибку времени компиляции:
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));
когда метод ожидает простой делегат?
7 ответов:
лямбда-выражение может быть преобразовано в тип делегата или дерево выражений - но оно должно знать , который тип делегата. Просто знать подпись недостаточно. Например, предположим, что у меня есть:
public delegate void Action1(); public delegate void Action2(); ... Delegate x = () => Console.WriteLine("hi");
что бы вы ожидаете конкретный тип объекта, на который ссылается
x
быть? Да, компилятор может создать новый тип делегата с соответствующей подписью, но это редко полезно, и вы в конечном итоге с меньшей возможностью для проверки ошибок.если вы хотите, чтобы сделать его легко назвать
Control.Invoke
СAction
самое простое, что нужно сделать, это добавить метод расширения для управления: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); }); }
для бонусных очков вот еще один совет. Вы не сделали бы этого для пользовательского интерфейса, но в тех случаях, когда вам нужно что-то заблокировать до его завершения (например, запрос/ответ ввода-вывода, ожидание ответа), используйте 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);}
я помещаю эти две функции в мое приложение формы, и я могу совершать звонки от фоновых работников, как это
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-адреса из gui DataGridView, пинг их, установить результирующий значки на зеленый или красный, а также кнопки повторного включения на форме. Да, это "параллель".Для" в backgroundworker. Да, это много накладных расходов на вызов, но их незначительно для коротких списков и гораздо более компактного кода.
я пытался построить это на @Андрей Наумов'ы ответ. Может быть это небольшое улучшение.
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>
etc.преимущество этого я вижу над оригинальным подходом:
на одну спецификацию типа меньше (необходимо указать только формальный параметр).
что дает вам свободу использовать его против любого
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);
это еще меньше набрав, но вы теряете определенную безопасность типа, и ИМО, это не стоит.