C# 为什么在作为普通 Delegate 参数提供时必须强制转换 lambda 表达式
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/411579/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why must a lambda expression be cast when supplied as a plain Delegate parameter
提问by xyz
Take the method System.Windows.Forms.Control.Invoke(Delegate method)
采取方法 System.Windows.Forms.Control.Invoke(Delegate method)
Why does this give a compile time error:
为什么会出现编译时错误:
string str = "woop";
Invoke(() => this.Text = str);
// Error: Cannot convert lambda expression to type 'System.Delegate'
// because it is not a delegate type
Yet this works fine:
然而这很好用:
string str = "woop";
Invoke((Action)(() => this.Text = str));
When the method expects a plain Delegate?
当方法需要一个普通的委托时?
采纳答案by Jon Skeet
A lambda expression can either be converted to a delegate type or an expression tree - but it has to know whichdelegate type. Just knowing the signature isn't enough. For instance, suppose I have:
lambda 表达式可以转换为委托类型或表达式树 - 但它必须知道哪种委托类型。仅仅知道签名是不够的。例如,假设我有:
public delegate void Action1();
public delegate void Action2();
...
Delegate x = () => Console.WriteLine("hi");
What would you expect the concrete type of the object referred to by x
to be? Yes, the compiler couldgenerate a new delegate type with an appropriate signature, but that's rarely useful and you end up with less opportunity for error checking.
您期望 引用的对象的具体类型x
是什么?是的,编译器可以生成具有适当签名的新委托类型,但这很少有用,而且您最终进行错误检查的机会较少。
If you want to make it easy to call Control.Invoke
with an Action
the easiest thing to do is add an extension method to Control:
如果你想很容易地调用Control.Invoke
与Action
最容易做的事情是添加扩展的方法来控制:
public static void Invoke(this Control control, Action action)
{
control.Invoke((Delegate) action);
}
回答by Jon Skeet
Tired of casting lambdas over and over?
厌倦了一遍又一遍地投射 lambdas?
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());
}
}
回答by Peter Wone
Nine tenths of the time people get this because they are trying to marshal onto the UI thread. Here's the lazy way:
十分之九的人得到这个是因为他们试图编组到 UI 线程。这是懒惰的方法:
static void UI(Action action)
{
System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(action);
}
Now that it's typed, the problem goes away (qv Skeet's anwer) and we have this very succinct syntax:
现在它被输入了,问题就消失了(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);
});
}
For bonus points here's another tip. You wouldn't do this for UI stuff but in cases where you need SomeMethod to block till it completes (eg request/response I/O, waiting for the response) use a WaitHandle(qv msdn WaitAll, WaitAny, WaitOne).
对于奖励积分,这是另一个提示。对于 UI 内容,您不会这样做,但是在需要 SomeMethod 阻塞直到它完成(例如请求/响应 I/O,等待响应)的情况下,请使用WaitHandle(qv msdn WaitAll、WaitAny、WaitOne)。
Note that AutoResetEvent is a WaitHandle derivative.
请注意, 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
}
And a final tip because things can get tangled: WaitHandles stall the thread. This is what they're supposed to do. If you try to marshal onto the UI thread while you have it stalled, your app will hang. In this case (a) some serious refactoring is in order, and (b) as a temporary hack you can wait like this:
还有最后一个提示,因为事情可能会变得混乱:WaitHandles 会拖延线程。这是他们应该做的。如果您尝试在 UI 线程停止时对其进行编组,则您的应用程序将挂起。在这种情况下 (a) 需要进行一些严重的重构,并且 (b) 作为临时黑客,您可以像这样等待:
bool wait = true;
ThreadPool.QueueUserWorkItem ((state) =>
{
//do asynch stuff
wait = false;
});
while (wait) Thread.Sleep(100);
回答by rocketsarefast
Peter Wone. you are da man. Taking your concept a bit further, I came up with these two functions.
彼得·沃恩。你是大男人。将你的概念更进一步,我想出了这两个函数。
private void UIA(Action action) {this.Invoke(action);}
private T UIF<T>(Func<T> func) {return (T)this.Invoke(func);}
I place these two functions into my Form app, and I can make calls from background workers like this
我将这两个函数放入我的 Form 应用程序中,我可以像这样从后台工作人员进行调用
int row = 5;
string ip = UIF<string>(() => this.GetIp(row));
bool r = GoPingIt(ip);
UIA(() => this.SetPing(i, r));
Maybe a bit lazy, but i don't have to setup worker done functions, which comes in super handy in cases such as this
也许有点懒,但我不必设置工人完成的功能,这在这种情况下非常方便
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));
}
Essentially, get some ip addresses from a gui DataGridView, ping them, set the resulting icons to green or red, and reenable buttons on the form. Yes, it is a "parallel.for" in a backgroundworker. Yes it is a LOT of invoking overhead, but its negligible for short lists, and much more compact code.
本质上,从 gui DataGridView 获取一些 ip 地址,ping 它们,将生成的图标设置为绿色或红色,然后重新启用表单上的按钮。是的,它是 backgroundworker 中的“parallel.for”。是的,它有很多调用开销,但对于短列表和更紧凑的代码可以忽略不计。
回答by nawfal
I tried to build this upon @Andrey Naumov's answer. May be this is a slight improvement.
我试图在@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;
}
}
Where type parameter S
is the formal parameter (the input parameter, which is minimum required to infer rest of the types). Now you can call it like:
其中类型参数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!");
You can have additional overloads for Action<S>
and Expression<Action<S>>
similarly in the same class. For otherbuilt in delegate and expression types, you will have to write separate classes like Lambda
, Lambda<S, T>
, Lambda<S, T, U>
etc.
您可以在同一个类中为Action<S>
和Expression<Action<S>>
类似地进行额外的重载。对于其他内置的委托和表达式类型,你将不得不编写不同的类,如Lambda
,Lambda<S, T>
,Lambda<S, T, U>
等。
Advantage of this I see over the original approach:
我在原始方法中看到的优点是:
One less type specification (only the formal parameter needs to be specified).
Which gives you the freedom to use it against any
Func<int, T>
, not just whenT
is say,string
, as shown in examples.Supports expressions straight away. In the earlier approach you will have to specify types again, like:
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!");
for expressions.
Extending the class for other delegate (and expression) types is similarly cumbersome like above.
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<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());
In my approach you have to declare types only once (that too one less for Func
s).
在我的方法中,您只需要声明一次类型(对于Func
s来说也少了一个)。
One another way to implement Andrey's answer is like not going fully 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;
}
So things reduce to:
所以事情减少到:
var l = Lambda<int>.Expression;
var e1 = l(x => x.ToString());
var e2 = l(x => "Hello!");
var e3 = l(x => x + x);
That's even less typing, but you lose certain type safety, and imo, this is not worth it.
这甚至更少打字,但你失去了一定的类型安全性,而且我觉得这是不值得的。
回答by Narottam Goyal
this.Dispatcher.Invoke((Action)(() => { textBox1.Text = "Test 123"; }));
回答by Tien Dinh
Bit late to the party but you can also cast like this
聚会有点晚,但你也可以这样施法
this.BeginInvoke((Action)delegate {
// do awesome stuff
});
回答by Fábio Augusto Pandolfo
Playing with XUnit and Fluent Assertionsit was possible to use this inline capability in a way I find really cool.
使用 XUnit 和Fluent Assertions,可以以一种我觉得非常酷的方式使用这种内联功能。
Before
前
[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.");
}