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

Возьмем метод System.Windows.Forms.Control.Invoke(метод делегата)

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

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

Когда метод ожидает простого делегата?

Ответ 1

Лямбда-выражение может быть преобразовано в тип делегата или дерево выражений - но он должен знать, какой тип делегата. Недостаточно знать подпись. Например, предположим, что у меня есть:

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

Ответ 2

Устали от литья лямбда снова и снова?

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

Ответ 3

Девять десятых того времени люди получают это, потому что они пытаются маршировать на поток пользовательского интерфейса. Здесь ленивый способ:

static void UI(Action action) 
{ 
  System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(action); 
}

Теперь, когда он печатается, проблема исчезает (qv Skeet anwer), и у нас есть этот очень сжатый синтаксис:

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

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

Ответ 4

Питер Вон. вы - человек. Взяв вашу концепцию немного дальше, я придумал эти две функции.

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, выполнить ping их, установить результирующие значки зеленым или красным цветом и повторно использовать кнопки в форме. Да, это "parallel.for" в фоновой работе. Да, это много, из-за чего накладные расходы, но его незначительные для коротких списков и гораздо более компактный код.

Ответ 5

Я попытался построить это в ответ @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> и т.д.

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

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

  • Это дает вам свободу использовать его против любого 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);

Это еще меньше печатает, но вы теряете определенную безопасность типов и imo, это не стоит.

Ответ 6

Бит опоздал на вечеринку, но вы также можете сделать это как

this.BeginInvoke((Action)delegate {
    // do awesome stuff
});

Ответ 7

 this.Dispatcher.Invoke((Action)(() => { textBox1.Text = "Test 123"; }));

Ответ 8

Играя с XUnit и Свободными утверждениями, можно было использовать эту встроенную возможность способом, который я нахожу действительно классным.

Перед

[Fact]
public void Pass_Open_Connection_Without_Provider()
{
    Action action = () => {
        using (var c = DbProviderFactories.GetFactory("MySql.Data.MySqlClient").CreateConnection())
        {
            c.ConnectionString = "<xxx>";
            c.Open();
        }
    };

    action.Should().Throw<Exception>().WithMessage("xxx");
}
После того, как

After

[Fact]
public void Pass_Open_Connection_Without_Provider()
{
    ((Action)(() => {
        using (var c = DbProviderFactories.GetFactory("<provider>").CreateConnection())
        {
            c.ConnectionString = "<connection>";
            c.Open();
        }
    })).Should().Throw<Exception>().WithMessage("Unable to find the requested .Net Framework Data Provider.  It may not be installed.");
}