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

发布于 2024-07-10 23:01:56 字数 404 浏览 4 评论 0 原文

采用方法 System.Windows.Forms.Control.Invoke(Delegate method)

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

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

当方法需要一个普通的委托时?

Take the 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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

半暖夏伤 2024-07-17 23:01:57

聚会有点晚了,但你也可以像这样投射

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

Bit late to the party but you can also cast like this

this.BeginInvoke((Action)delegate {
    // do awesome stuff
});
空名 2024-07-17 23:01:57
 this.Dispatcher.Invoke((Action)(() => { textBox1.Text = "Test 123"; }));
 this.Dispatcher.Invoke((Action)(() => { textBox1.Text = "Test 123"; }));
萌无敌 2024-07-17 23:01:57

使用 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.");
}

Playing with XUnit and Fluent Assertions it was possible to use this inline capability in a way I find really cool.

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.");
}
哀由 2024-07-17 23:01:57

其他答案在编写时是正确的,但从 C# 10.0(从 2021 年起)开始,编译器可以推断出合适的委托类型(例如某些 Func<...>< /code>、Action<...> 或生成的委托类型)在这种情况下。

请参阅 C# 10 功能 - Lambda改进

string str = "woop";
Invoke(() => this.Text = str);   // OK in C# 10.0, in this case 'Func<string>' is picked
string str = "woop";
Invoke(() => { this.Text = str; });   // OK in C# 10.0, in this case 'Action' is picked

注释假定签名 Invoke(Delegate method) 如您的问题中所示。 当然,如果该方法需要特定的(非抽象)委托类型,C# 将尝试将 lambda 转换为该类型,C# 10.0 之前的情况也是如此。

Other answers were correct at the time they were written, but starting from C# 10.0 (from 2021), the compiler can infer a suitable delegate type (like some Func<...>, Action<...> or generated delegate type) in such cases.

See C# 10 Features - Lambda improvements.

string str = "woop";
Invoke(() => this.Text = str);   // OK in C# 10.0, in this case 'Func<string>' is picked
string str = "woop";
Invoke(() => { this.Text = str; });   // OK in C# 10.0, in this case 'Action' is picked

Comments assume the signature Invoke(Delegate method) as in your question. Of course if the method wants a particular (not abstract) delegate type, C# will attempt to convert the lambda into that one, as was also the case before C# 10.0.

套路撩心 2024-07-17 23:01:56

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

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

...

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

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

如果您想轻松地使用 Action 调用 Control.Invoke,最简单的方法就是向 Control 添加扩展方法:

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

A lambda expression can either be converted to a delegate type or an expression tree - but it has to know which delegate type. Just knowing the signature isn't enough. For instance, suppose I have:

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 could generate a new delegate type with an appropriate signature, but that's rarely useful and you end up with less opportunity for error checking.

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:

public static void Invoke(this Control control, Action action)
{
    control.Invoke((Delegate) action);
}
得不到的就毁灭 2024-07-17 23:01:56

厌倦了一遍又一遍地转换 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());
    }
}

Tired of casting lambdas over and over?

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());
    }
}
要走干脆点 2024-07-17 23:01:56

十分之九的情况下,人们会遇到这种情况,因为他们正在尝试编组到 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 线程停止时尝试封送至 UI 线程,您的应用程序将挂起。 在这种情况下,(a)需要进行一些认真的重构,(b)作为临时黑客,您可以像这样等待:

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

Nine tenths of the time people get this because they are trying to marshal onto the UI thread. Here's the lazy way:

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:

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

Note that AutoResetEvent is a WaitHandle derivative.

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:

  bool wait = true;
  ThreadPool.QueueUserWorkItem ((state) =>
  {
    //do asynch stuff        
    wait = false;
  });
  while (wait) Thread.Sleep(100);
行雁书 2024-07-17 23:01:56

彼得·沃恩. 你是个大男人。
进一步阐述你的概念,我想出了这两个函数。

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

本质上,从 gui DataGridView 获取一些 IP 地址,对它们执行 ping 操作,将生成的图标设置为绿色或红色,然后重新启用表单上的按钮。 是的,它是后台工作者中的“parallel.for”。 是的,这是大量的调用开销,但对于短列表和更紧凑的代码来说,它可以忽略不计。

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

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.

痴者 2024-07-17 23:01:56

我尝试根据 @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!");

您可以在同一个类中类似地为 ActionExpression> 提供额外的重载。 对于其他内置委托和表达式类型,您必须编写单独的类,例如 LambdaLambda Lambda 等。

与原始方法相比,我认为这样做的优点是:

  1. 少了一个类型规范(仅需要指定形式参数)。

  2. 这让您可以自由地将它用于任何 Func,而不仅仅是当 Tstring 时,如示例所示。

  3. 立即支持表达式。 在之前的方法中,您必须再次指定类型,例如:

    var e = Lambda<表达式>>.Cast(x => "Hello!"); 
    
      //或者如果“Cast”是非泛型“Lambda”类的实例成员: 
      var e = lambda.Cast<表达式>>(x => "Hello!"); 
      

    用于表达式。

  4. 为其他委托(和表达式)类型扩展类与上面类似很麻烦。

    var e = Lambda>.Cast(x => x.ToString()); 
    
      //或者对于表达式>   如果“Cast”是非泛型“Lambda”类的实例成员: 
      var e = lambda.Cast<表达式>>(x => x.ToString()); 
      

在我的方法中,您只需声明类型一次(对于 Func 来说也少声明一次)。


实现安德烈的答案的另一种方法是不完全通用,

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

这甚至更少的打字,但你失去了一定的类型安全性,我认为,这是不值得的。

I tried to build this upon @Andrey Naumov's answer. May be this is a slight improvement.

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:

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 other built in delegate and expression types, you will have to write separate classes like Lambda, Lambda<S, T>, Lambda<S, T, U> etc.

Advantage of this I see over the original approach:

  1. One less type specification (only the formal parameter needs to be specified).

  2. Which gives you the freedom to use it against any Func<int, T>, not just when T is say, string, as shown in examples.

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

  4. 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());
    

In my approach you have to declare types only once (that too one less for Funcs).


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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文