为什么在作为普通委托参数提供时,必须将lambda表达式进行强制转换?

25 浏览
0 Comments

为什么在作为普通委托参数提供时,必须将lambda表达式进行强制转换?

为什么这段代码会产生编译时错误:

string str = "woop";
Invoke(() => this.Text = str);
// 错误:无法将 lambda 表达式转换为类型 'System.Delegate',因为它不是委托类型

但这段代码却正常运行:

string str = "woop";
Invoke((Action)(() => this.Text = str));

当方法期望一个普通的 Delegate 时?

0
0 Comments

为什么在作为普通委托参数时必须对lambda表达式进行转换?

在上面的代码中,Lambda类定义了一个静态的Func类型的委托Cast,它的实现很简单,就是将输入的参数返回。在Example类中的Run方法中,使用了Lambda>.Cast来声明一个变量c,然后通过c(x => x.ToString())等形式使用lambda表达式创建了一些委托实例f1、f2等。

这个Lambda类的设计巧妙之处在于,它通过泛型的方式将输入和输出的类型参数化,从而实现了在使用lambda表达式时不再需要进行类型转换的便利。Lambda类的Cast方法接受任意类型的委托作为参数,并返回相同类型的委托,这样就避免了在每次使用lambda表达式时都需要手动进行类型转换的麻烦。

在Example类中的Run方法中,使用了Lambda>.Cast来声明了一个变量c,这个变量c实际上就是一个委托,它的类型是Func。然后通过c(x => x.ToString())等形式使用lambda表达式来创建了一些委托实例f1、f2等,这些委托实例的类型都是Func,因为c的类型是Func,所以在使用lambda表达式时,编译器会自动推断出委托的类型,并将lambda表达式转换为相应的委托实例。

Lambda类通过泛型和委托的结合,实现了在使用lambda表达式时不再需要进行类型转换的便利。通过声明一个泛型委托变量c,并使用Lambda.Cast方法将lambda表达式转换为相应的委托实例,可以避免在每次使用lambda表达式时都需要手动进行类型转换的繁琐操作。这样就提高了代码的可读性和开发效率。

0
0 Comments

为什么在作为普通委托参数提供时,lambda表达式必须进行强制转换?

lambda表达式可以转换为委托类型或表达式树,但它必须知道具体的委托类型。仅仅知道签名是不够的。例如,假设我有以下代码:

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

谢谢,我更新了问题,因为我认为“untyped”不是正确的术语。

这是一个非常优雅和成熟的解决方案。我可能会将其命名为“InvokeAction”,以便该名称暗示我们实际调用的内容(而不是一个通用委托),但对我来说肯定可行。

我不同意它“很少有用...”。在使用lambda调用Begin/Invoke的情况下,你确实不关心委托类型是否是自动生成的,我们只是希望进行调用。在什么情况下,接受委托(基本类型)的方法会关心具体的类型是什么?此外,扩展方法的目的是什么?它并没有使任何事情变得更容易。

扩展方法确实使事情变得更容易。你可以直接调用`control.Invoke(() => DoStuff)`,因为从lambda表达式到`Action`有一个转换。这不正是我们想要的吗?至于“自动生成”的委托是否有用 - 我认为问题在于`Delegate`实际上并不合适。如果给`Control.Invoke`传递了一个具有10个参数的委托,它应该传递什么?在.NET 1中,它是最好的选择,但是使用泛型后,我认为有更好的方法。

啊!我添加了扩展方法并尝试了`Invoke(() => DoStuff)`,仍然出现错误。问题在于我使用了隐式的`this`。要在控件成员内部使其工作,你必须明确使用:`this.Invoke(() => DoStuff)`。

对其他人来说,我认为阅读此问题和答案的C#: Automating the InvokeRequired code pattern非常有帮助。

这是一个很好的答案!类似地,你不能直接将匿名委托转换为`LambdaExpression`,而是必须使用指定类型的确切表达式进行转换。

0
0 Comments

lambda表达式必须在作为普通委托参数传递时进行强制转换的原因是,大部分情况下,人们之所以遇到这个问题是因为他们想要将代码调度到UI线程。通过下面的代码可以解决这个问题:

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方法完成之前阻塞(例如请求/响应I/O,等待响应),可以使用WaitHandle。注意,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
}

在处理代码会变得混乱的情况下,WaitHandle会阻塞线程。如果您尝试在UI线程上进行调度而同时阻塞该线程,您的应用程序将挂起。在这种情况下,需要进行一些严重的重构,并且作为一个临时解决方法,可以使用以下方式进行等待:

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

令人惊讶的是,有些人会因为个人不喜欢而对答案进行投票否定。如果答案是错误的,并且你知道这一点,请指出其中的错误之处。如果你无法做到这一点,那么你就没有投票的依据。如果答案是极其错误的,那么可以说一些类似于“胡说八道。参见[正确的回答]”或者“不推荐的解决方案,请参见[更好的解决方案]”之类的话。

System.Windows.Threading.Dispatcher.CurrentDispatcher

将返回当前线程的调度器,即如果您从非UI线程调用此方法,代码将不会在UI线程上运行。

好的建议是,应用程序应该捕获对UI线程调度器的引用,并将其放在全局可访问的位置。令人惊讶的是,花了这么长时间才有人注意到这一点!

0