Почему лямбда-выражение должно быть приведено в качестве простого параметра делегата

возьмите систему методов.Окна.Формы.Управление.Вызов(метод делегата)

почему это дает ошибку времени компиляции:

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 останавливают нить. Это то, что они должны делать. Если вы попытаетесь маршалировать в поток UI пока вы его остановили ваше приложение будет висеть. В этом случае (а) некоторые серьезные рефакторинга в порядке, и (Б) в качестве временного взлома вы можете ждать, как это:

  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. Да, это много накладных расходов, но его пренебрежимо мало для коротких списков и гораздо более компактного кода.


я пытался построить это на @Andrey Naumov'ы ответ. Может быть это небольшое улучшение.

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.

преимущество этого я вижу над оригинальным подходом:

  1. на одну спецификацию типа меньше (необходимо указать только формальный параметр).

  2. что дает вам свободу использовать его против любого Func<int, T>, а не только когда T сказать, string, как показано в примерах.

  3. поддерживает выражения сразу. В более ранний подход вам придется снова указать типы, например:

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

    для выражения.

  4. расширение класса для других типов делегатов (и выражений) аналогично громоздким, как указано выше.

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

в моем подходе вы должны объявлять типы только один раз (что тоже на один меньше для Funcs).


еще один способ реализовать ответ Андрея-это не полностью generic

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