Invokeメソッドを使用するとコンパイルが正常になり、Func <int、int>を直接返すと正常にコンパイルできないのはなぜですか?


28

私はこのケースを理解していません:

public delegate int test(int i);

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
}

public test Fail()
{
    Func<int, int> f = x => x;
    return f; // <- code doesn't compile
}

Invokeメソッドを使用するとコンパイルが正常になり、csharp Func<int,int>直接戻るとコンパイルできないのはなぜですか?


あなたはデリゲートを持っています。つまり、ある種のイベントを受け取っています。Invokeは、スレッド間の例外を防ぎ、複数のプロセスがオブジェクトにアクセスできるようにします。
jdweng

delegate void test1(int i);and などの同じように見える2つのデリゲートを使用しても、この問題が発生することに注意してくださいdelegate void test2(int i);
マシューワトソン

回答:


27

この動作を理解するには、2つのことを知っておく必要があります。

  1. すべてのデリゲートはから派生しますがSystem.Delegate、デリゲートはタイプが異なるため、互いに割り当てることができません。
  2. C#言語は、メソッドまたはラムダをデリゲートに割り当てるための特別な処理を提供します

デリゲートはタイプが異なるため、あるタイプのデリゲートを別のタイプに割り当てることはできません。

たとえば、次の場合:

delegate void test1(int i);
delegate void test2(int i);

次に:

test1 a = Console.WriteLine; // Using special delegate initialisation handling.
test2 b = a;                 // Using normal assignment, therefore does not compile.

上記の最初の行は、ラムダまたはメソッドをデリゲートに割り当てるための特別な処理を使用しているため、正常にコンパイルされます。

実際、この行はコンパイラーによって次のように効率的に書き換えられます。

test1 a = new test1(Console.WriteLine);

上記の2行目は、あるタイプのインスタンスを別の互換性のないタイプに割り当てようとしているため、コンパイルされません。

タイプに関しては、test1との間には互換性のある割り当てはありませんtest2。それらは異なるタイプだからです。

それについて考えることが役立つ場合は、次のクラス階層を検討してください。

class Base
{
}

class Test1 : Base
{
}

class Test2 : Base
{
}

次のコードは、コンパイルしていてもしませんTest1し、Test2同じ基本クラスから派生します:

Test1 test1 = new Test1();
Test2 test2 = test1; // Compile error.

これは、あるデリゲートタイプを別のデリゲートタイプに割り当てることができない理由を説明しています。これは通常のC#言語です。

ただし、重要なことは、互換性のあるデリゲートにメソッドまたはラムダを割り当てることが許可される理由を理解することです。上記のように、これはデリゲートのC#言語サポートの一部です。

最後にあなたの質問に答えます:

を使用Invoke()すると、互換性のない型を割り当てようとするのではなく、メソッドまたはラムダをデリゲートに割り当てるための特別なC#言語処理を使用して、メソッド呼び出しをデリゲートに割り当てます。したがって、コンパイルはOKです。

完全に明確にするために、OPでコンパイルするコード:

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
}

概念的には次のように変換されます:

public test Success()
{
    Func<int, int> f = x => x;
    return new test(f.Invoke);
}

失敗したコードは2つの互換性のないタイプ間で割り当てを試みていますが、

public test Fail()
{
    Func<int, int> f = x => x;
    return f; // Attempting to assign one delegate type to another: Fails
}

6

2番目のケースでfは、型Func<int, int>はですが、メソッドはを返すと言われていtestます。これらは互いに関連しない(デリゲート)タイプであり、相互に変換できないため、コンパイラエラーが発生します。言語仕様のこのセクションに移動して、「delegate」を検索できます。同じ署名を持つデリゲート間の変換についての言及はありません。

ただし、最初のケースでf.Invokeは、メソッドグループ式であり、実際には型がありません。C#コンパイラは、メソッドグループ変換を介して、コンテキストに応じてメソッドグループ式を特定のデリゲート型に変換します。

ここで5番目の箇条書きを引用して、強調してください)

式は次のいずれかに分類されます。

  • ...

  • メソッドグループ。これは、メンバーのルックアップの結果としてオーバーロードされたメソッドのセットです。[...]メソッドグループは、invocation_expression、delegate_creation_expression isおよび演算子の左側として許可され、暗黙的に互換性のあるデリゲート型に変換できます。

この場合、testデリゲート型に変換されます。

つまり、すでに型があるreturn fため機能しませんが、まだ型はありません。ff.Invoke


2

ここでの問題はタイプの互換性です:

以下は、MSDNソースからのFuncデリゲートの定義です。

public delegate TResult Func<in T, out TResult>(T arg);

上記のFuncと定義したデリゲートの間に直接の関係がないことがわかった場合:

public delegate int test(int i);

1番目のスニペットがコンパイルされる理由:

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
 }

デリゲートは、入力パラメーターと出力結果であるシグネチャを使用して比較されます。最終的に、デリゲートは関数ポインターであり、2つの関数はシグネチャを介してのみ比較できます。実行時に、Funcを介して呼び出されたメソッドがTestデリゲートに割り当てられます。これは、Signatureが同じであるため、シームレスに機能します。これは関数ポインタの割り当てであり、TestデリゲートはFuncデリゲートが指すメソッドを呼び出すようになります。

2番目のスニペットがコンパイルに失敗する理由

Funcとテストデリゲートの間では、型/割り当ての互換性はありません。Funcは型システムルールの一部として入力できません。test delegate最初の場合と同様に、その結果を割り当てて入力できる場合でも。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.