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

Возьмите метод 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));

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


c# delegates lambda c#-3.0
person xyz    schedule 04.01.2009    source источник


Ответы (7)


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

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);
}
person Jon Skeet    schedule 04.01.2009
comment
Спасибо. Я обновил вопрос, потому что считаю, что термин «нетипизированный» был неправильным. - person xyz; 04.01.2009
comment
Это очень элегантное и продуманное решение. Я бы, наверное, назвал это InvokeAction, чтобы имя подсказывало, что мы на самом деле вызываем (вместо общего делегата), но у меня это определенно работает :) - person Matthias Hryniszak; 07.10.2010
comment
Я не согласен с тем, что это редко бывает полезно и .... В случае вызова Begin / Invoke с лямбда-выражением вам, конечно, безразлично, генерируется ли тип делегата автоматически, мы просто хотим, чтобы вызов был выполнен. В какой ситуации метод, принимающий делегата (базовый тип), будет заботиться о том, что это за конкретный тип? Кроме того, какова цель метода расширения? Нет ничего проще. - person Tergiver; 07.01.2011
comment
@Tergiver: метод расширения, безусловно, действительно упрощает задачу. Вы можете просто вызвать control.Invoke(() => DoStuff), потому что существует преобразование лямбда-выражения в Action. Разве это не то, что мы хотим? Что касается того, полезен ли автоматически сгенерированный делегат - я бы сказал, что проблема здесь в том, что Delegate действительно вообще не подходит. Что должно Control.Invoke передать, если вы дадите ему делегат с 10 параметрами? Это был лучший вариант, доступный в .NET 1, но с дженериками есть лучшие подходы IMO. - person Jon Skeet; 07.01.2011
comment
Ах! Я добавил метод расширения и попробовал Invoke(()=>DoStuff), но все равно получил ошибку. Проблема заключалась в том, что я использовал неявное «это». Чтобы заставить его работать из элемента Control, вы должны четко указать: this.Invoke(()=>DoStuff). - person Tergiver; 07.01.2011
comment
Для всех, кто это читает, я думаю, очень полезны вопросы и ответы на C #: автоматизация шаблона кода InvokeRequired. - person Erik Philips; 09.01.2013
comment
Хороший ответ! Точно так же вы не можете преобразовать анонимный делегат непосредственно в LambdaExpression, вместо этого нужно преобразовать точное выражение с указанными типами - person nawfal; 27.04.2013

Устали многократно использовать лямбды?

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());
    }
}
person Community    schedule 03.04.2009
comment
Это прекрасное использование дженериков. - person Peter Wone; 10.11.2011
comment
Должен признаться, мне потребовалось некоторое время, чтобы понять, почему это сработало. Блестяще. Жаль, что мне это сейчас не нужно. - person William; 18.12.2012
comment
Не могли бы вы объяснить, как это использовать? Мне сложно это понять? Большое спасибо. - person shahkalpeshp; 23.01.2013
comment
Его нужно когда-либо читать, не говоря уже о том, чтобы сказать это, но я думаю, что предпочитаю этот ответ Джона Скиту! - person Pogrindis; 30.09.2013
comment
@shahkalpesh это не очень сложно. Посмотрите на это так: у класса Lambda<T> есть метод преобразования идентификаторов, называемый Cast, который возвращает все, что было передано (Func<T, T>). Теперь Lambda<T> объявлен как Lambda<Func<int, string>>, что означает, что если вы передадите метод Func<int, string> в Cast, он вернет Func<int, string> обратно, поскольку T в этом случае Func<int, string>. - person nawfal; 16.01.2014
comment
Итак, теперь переменная c является делегатом функции Cast. Он принимает на входе T, который равен Func<int, string>, поэтому они передаются как аргументы в форме лямбда (c(.. => ...)), который возвращает то же значение, которое знает f1, f2 и т. Д. В основном вы полагаетесь на механизм вывода компилятора. - person nawfal; 16.01.2014

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

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);
person Peter Wone    schedule 10.11.2011
comment
Я нахожу удивительным то, что люди имеют наглость отклонять ответ только потому, что лично им это не нравится. Если это неправильно и вы это знаете, тогда скажите, что с этим не так. Если вы не можете этого сделать, значит, у вас нет оснований для отрицательного голоса. Если это эпически неверно, скажите что-нибудь вроде вздора. См. [Правильный ответ] или, возможно, Не рекомендуемое решение, см. [Лучшие материалы] - person Peter Wone; 01.08.2012
comment
Да, я откровенная пряжка; но в любом случае я понятия не имею, почему он был отклонен; хотя я не использовал реальный код, я подумал, что это хорошее краткое введение в межпоточные вызовы пользовательского интерфейса, и в нем есть некоторые вещи, о которых я действительно не думал, так что спасибо, определенно +1 за то, что делал все возможное. :) Я имею в виду, что вы дали хороший быстрый метод для вызова делегатов; вы даете возможность звонков, которые нужно дождаться; и вы следуете за этим хорошим быстрым способом для тех, кто застрял в UI Thread Hell, чтобы вернуть немного контроля. Прекрасный ответ, я тоже скажу + ‹3. :) - person shelleybutterfly; 09.11.2012
comment
System.Windows.Threading.Dispatcher.CurrentDispatcher вернет диспетчер ТЕКУЩЕГО потока, т.е. если вы вызовете этот метод из потока, который не является потоком пользовательского интерфейса, код не будет запущен в потоке пользовательского интерфейса. - person BrainSlugs83; 26.05.2018
comment
@ BrainSlugs83 - хороший момент. Возможно, лучше всего для приложения захватить ссылку на диспетчер потока пользовательского интерфейса и разместить его где-нибудь в глобальном масштабе. Я удивлен, что кому-то понадобилось столько времени, чтобы это заметить! - person Peter Wone; 27.05.2018

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

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

person rocketsarefast    schedule 07.12.2012

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

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

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


Еще один способ реализовать ответ Андрея - не стать полностью универсальным.

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

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

person nawfal    schedule 16.12.2013

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

this.BeginInvoke((Action)delegate {
    // do awesome stuff
});
person Tien Dinh    schedule 17.07.2015

Играя с XUnit и Fluent Assertions, можно было использовать эту встроенную возможность, что я считаю очень крутым.

До

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

После

[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.");
}
person Fábio Augusto Pandolfo    schedule 29.08.2019