126

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

メソッドがプレーンなデリゲートを期待するのはいつですか?

4

8 に答える 8

126

ラムダ式は、デリゲート型または式ツリーのいずれかに変換できますが、どちらのデリゲート型かを認識している必要があります。署名を知っているだけでは十分ではありません。たとえば、私が持っているとしましょう:

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);
}
于 2009-01-04T20:07:02.407 に答える
34

ラムダを何度もキャストするのにうんざりしていませんか?

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());
    }
}
于 2009-04-03T14:04:11.780 に答える
12

90 分の 1 の確率で、UI スレッドにマーシャリングしようとしているために、これが発生します。怠惰な方法は次のとおりです。

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

入力されたので、問題はなくなり (qv Skeet's anwer)、次の非常に簡潔な構文が得られます。

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);
于 2011-11-10T00:41:32.197 に答える
4

ピーター・ウォン。あなたは男です。あなたのコンセプトをもう少し進めて、私はこれらの2つの機能を思いつきました。

private void UIA(Action action) {this.Invoke(action);}
private T UIF<T>(Func<T> func) {return (T)this.Invoke(func);}

これら 2 つの関数を Form アプリに配置すると、このようにバックグラウンド ワーカーから呼び出しを行うことができます。

int row = 5;
string ip = UIF<string>(() => this.GetIp(row));
bool r = GoPingIt(ip);
UIA(() => this.SetPing(i, r));

少し怠け者かもしれませんが、worker done 関数をセットアップする必要はありません。これは、このような場合に非常に便利です。

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 を実行し、結果のアイコンを緑または赤に設定し、フォームのボタンを再度有効にします。はい、backgroundworker の「parallel.for」です。はい、多くの呼び出しオーバーヘッドがありますが、短いリストとはるかにコンパクトなコードでは無視できます。

于 2012-12-07T01:45:21.630 に答える
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;
    }
}

ここで、型パラメーター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. 型指定が 1 つ減ります (仮パラメーターのみを指定する必要があります)。

  2. これにより、例に示すように、が言うFunc<int, T>ときだけでなく、任意の に対して自由に使用できます。Tstring

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

私のアプローチでは、タイプを 1 回だけ宣言する必要があります ( Funcs の場合も 1 つ少なくなります)。


アンドレイの答えを実装するもう1つの方法は、完全に一般化しないようなものです

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

それはさらにタイピングを減らしますが、特定の型の安全性を失います。そして、これは価値がありません.

于 2013-12-16T10:15:43.217 に答える
1

パーティーに少し遅れましたが、このようにキャストすることもできます

this.BeginInvoke((Action)delegate {
    // do awesome stuff
});
于 2015-07-17T07:56:35.323 に答える