当作为纯Delegate参数提供时,为什么必须强制转换lambda表达式


124

采取方法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));

该方法何时需要普通代表?

Answers:


125

Lambda表达式可以转换为委托类型或表达式树-但它必须知道哪种委托类型。仅知道签名是不够的。例如,假设我有:

public delegate void Action1();
public delegate void Action2();

...

Delegate x = () => Console.WriteLine("hi");

您期望所指对象的具体类型x是什么?是的,编译器可以生成带有适当签名的新委托类型,但这很少有用,并且您进行错误检查的机会也会减少。

如果你想很容易地调用Control.InvokeAction最容易做的事情是添加扩展的方法来控制:

public static void Invoke(this Control control, Action action)
{
    control.Invoke((Delegate) action);
}

1
谢谢-我更新了问题,因为我认为未键入是使用错误的术语。
xyz

1
这是一个非常优雅和成熟的解决方案。我可能会称其为“ InvokeAction”,以便该名称表明我们实际上正在调用的内容(而不是泛型委托),但它确实对我
有用

7
我不同意它“非常有用而且...”。在使用lambda调用Begin / Invoke的情况下,您当然不在乎委托类型是否自动生成,我们只想进行调用即可。在什么情况下,接受委托(基本类型)的方法将关心什么是具体类型?另外,扩展方法的目的是什么?它并没有使任何事情变得容易。
Tergiver,2011年

5
啊! 我添加了扩展方法并尝试Invoke(()=>DoStuff)仍然出现错误。问题是我使用了隐式的“ this”。要使其在Control成员中正常工作,您必须明确::this.Invoke(()=>DoStuff)
Tergiver 2011年

2
对于其他阅读本文的人,我认为C#的问题与解答:自动化InvokeRequired代码模式非常有帮助。
Erik Philips

34

厌倦了一次又一次地铸造lambda?

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
那是泛型的漂亮用法。
Peter Wone

2
我不得不承认,这花了我一段时间才弄清楚它为什么起作用。辉煌。太糟糕了,我现在没有用。
威廉

1
你能解释一下这个用法吗?我很难理解这个吗?非常感谢。
shahkalpesh

它曾经读过这个,更不用说了,但是我想我比乔恩·斯基特更喜欢这个答案!
Pogrindis 2013年

@shahkalpesh它不是很复杂。这样看来,Lambda<T>该类有一个称为的身份转换方法Cast,该方法返回传递的任何内容(Func<T, T>)。现在将Lambda<T>声明为Lambda<Func<int, string>>,这意味着如果您传递Func<int, string>to Cast方法,它将返回Func<int, string>,因为T在这种情况下为Func<int, string>
nawfal 2014年

12

人们十分之一的时间收到此消息,是因为他们试图封送至UI线程。这是懒惰的方式:

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

现在已经输入了,问题就解决了(qv Skeet的答案),我们的语法非常简洁:

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

对于奖励积分,这是另一个提示。您不会对UI内容执行此操作,但是在需要SomeMethod阻止它完成之前(例如,请求/响应I / O,等待响应)的情况下,请使用 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线程,应用程序将挂起。在这种情况下(a)进行了一些认真的重构,并且(b)作为临时破解,您可以像这样等待:

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

3
我很着迷,因为人们个人认为该答案没有吸引力,因此他们有拒绝投票的兴趣。如果错了,而且您知道这一点,请说出问题所在。如果您不能这样做,那么您就没有理由投反对票。如果确实存在错误,请说“ Baloney。请参阅[正确的回答]”,或者说“不建议的解决方案,请参阅[更好的内容]”
Peter Wone 2012年

1
是的,我是frankenthreadstress。但是无论如何,我不知道为什么它被否决了。尽管我没有使用实际的代码,但我认为这是UI跨线程调用的快速入门,并且有些事情我还没真正想到过,因此肯定比+1更好。:)我的意思是,您提供了一个不错的快速方法来进行委托调用;您可以选择必须等待的呼叫;并且您可以采用一种不错的快速方法来跟踪陷入UI线程地狱的人,以获取一些控制权。好答案,我也要说+ <3。:)
shelleybutterfly 2012年

System.Windows.Threading.Dispatcher.CurrentDispatcher将返回CURRENT线程的调度程序-即,如果从不是UI线程的线程调用此方法,则该代码将不会在UI线程上运行。
BrainSlugs83 '18

@ BrainSlugs83好点,也许最好的事情是应用程序捕获对UI线程分派器的引用,并将其放在可全局访问的位置。我很惊讶有人花了这么长时间才注意到这一点!
Peter Wone

4

彼得·沃恩。你是个男人 使您的概念更进一步,我提出了这两个功能。

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

本质上,从gui DataGridView获取一些ip地址,对其进行ping操作,将结果图标设置为绿色或红色,然后重新启用表单上的按钮。是的,它是后台工作人员中的“ parallel.for”。是的,这会消耗很多开销,但是对于短列表和更紧凑的代码而言,它可以忽略不计。


1

我试图以@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;
    }
}

其中type参数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>>同样在同一个班。对于其他内置的委托和表达式类型,你就必须编写单独的类一样LambdaLambda<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);

打字的次数更少了,但是您失去了某些类型的安全性,imo,这是不值得的。


1

参加聚会有点晚,但是你也可以这样

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


0

玩XUnit和Fluent断言,可以以我觉得很酷的方式使用此内联功能。

之前

[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.");
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.