12

次のコード例を検討してください。

static void Main(string[] args)
{
   bool same = CreateDelegate(1) == CreateDelegate(1);
}

private static Action CreateDelegate(int x)
{
   return delegate { int z = x; };
}

古き良き名前付きメソッド アプローチ (new Action(MyMethod)) を使用する場合と同様に、2 つのデリゲート インスタンスを比較すると等しいと想像できます。.NET Framework はデリゲート インスタンスごとに非表示のクロージャ インスタンスを提供するため、両者は等しいとは言えません。これら 2 つのデリゲート インスタンスはそれぞれ、Target プロパティが個別の隠しインスタンスに設定されているため、比較されません。考えられる解決策の 1 つは、匿名メソッド用に生成された IL が現在のインスタンス (このポインター) をデリゲートのターゲットに格納することです。これにより、デリゲートを正しく比較できます。また、隠しクラスではなく、クラスがターゲットであることがわかるため、デバッガーの観点からも役立ちます。

この問題の詳細については、私が Microsoft に提出したバグを参照してください。バグ レポートには、この機能を使用している理由と、変更する必要がある理由の例も示されています。これも問題だと思われる場合は、評価と検証を提供してサポートしてください。

https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=489518

機能を変更すべきではない理由が考えられますか? これが問題を解決するための最善の行動だったと思いますか、それとも別の方法を取ることをお勧めしますか?

4

5 に答える 5

19

これを「バグ」と考えるのはあまり好きではありません。さらに、単に存在しない CLR の動作を想定しているようです。

ここで理解しておくべき重要なことは、メソッドを呼び出すたびに新しい匿名メソッドを返す (そして新しいクロージャ クラスを初期化する) ことですCreateDelegatedelegate内部で匿名メソッドに何らかのプールを使用するキーワードを熟知しているようです。CLR は確かにこれを行いません。匿名メソッドへのデリゲート (ラムダ式の場合と同様) は、メソッドを呼び出すたびにメモリ内に作成されます。もちろん、等値演算子はこの状況で参照falseを比較するため、返されるのは予想される結果です。

提案された動作は特定のコンテキストでいくつかの利点がある場合がありますが、実装が非常に複雑になる可能性があり、予測できないシナリオにつながる可能性が高くなります。呼び出しごとに新しい匿名メソッドとデリゲートを生成する現在の動作は正しいと思います。これは、Microsoft Connect についても同様のフィードバックが得られると思います。

質問で説明した動作に非常に固執している場合は、関数をメモ化するオプションが常にありますCreateDelegate。これにより、同じパラメーターに対して毎回同じデリゲートが返されることが保証されます。実際、これは実装が非常に簡単であるため、Microsoft が CLR での実装を検討しなかったいくつかの理由の 1 つです。

于 2009-09-14T17:40:06.747 に答える
5

編集:線の下の歴史的価値のために残された古い答え...

CLRは、キャプチャされた変数で実行できるすべてのことを考慮して、非表示のクラスが等しいと見なされる可能性があるケースを解決する必要があります。

この特定のケースでは、キャプチャされた変数(x)は、デリゲート内でもキャプチャコンテキストでも変更されませんが、言語がこのような複雑な分析を必要としないようにしたいと思います。言語が複雑になるほど、理解しにくくなります。この場合と、キャプチャされた変数の値が呼び出しごとに変更される以下の場合とを区別する必要があります。そこでは、どのデリゲートを呼び出すかによって大きな違いが生じます。それらは決して等しくありません。

このすでに複雑な状況(閉鎖はしばしば誤解されている)があまりにも「賢く」なりすぎて潜在的な平等を解決しようとしないことは完全に理にかなっていると思います。

IMO、あなたは間違いなく別のルートを取るべきです。これらは、の概念的に独立したインスタンスですAction。デリゲートターゲットを強制することによってそれを偽造することは、恐ろしいハックIMOです。


x問題は、生成されたクラスでの値をキャプチャしていることです。2つのx変数は独立しているため、不平等なデリゲートです。独立性を示す例を次に示します。

using System;

class Test
{
    static void Main(string[] args)
    {
        Action first = CreateDelegate(1);
        Action second = CreateDelegate(1);
        first();
        first();
        first();
        first();
        second();
        second();
    }

    private static Action CreateDelegate(int x)
    {
        return delegate 
        { 
            Console.WriteLine(x);
            x++;
        };
    }
}

出力:

1
2
3
4
1
2

編集:別の見方をすると、元のプログラムは次のものと同等でした:

using System;

class Test
{
    static void Main(string[] args)
    {
        bool same = CreateDelegate(1) == CreateDelegate(1);
    }

    private static Action CreateDelegate(int x)
    {
        return new NestedClass(x).ActionMethod;
    }

    private class Nested
    {
        private int x;

        internal Nested(int x)
        {
            this.x = x;
        }

        internal ActionMethod()
        {
            int z = x;
        }
    }
}

お分かりのように、の2つの別々のインスタンスNestedが作成され、それらが2つのデリゲートのターゲットになります。彼らは等しくないので、代表者も等しくありません。

于 2009-09-14T17:51:45.020 に答える
4

この問題の C# 固有の詳細についてはわかりませんが、同じ動作をする VB.Net の同等の機能に取り組みました。

要するに、この動作は次の理由から「設計による」ものです。

1 つ目は、このシナリオでは閉鎖が避けられないということです。匿名メソッド内でローカル データの一部を使用したため、状態を取得するにはクロージャが必要です。このメソッドを呼び出すたびに、さまざまな理由から新しいクロージャーを作成する必要があります。したがって、各デリゲートはそのクロージャのインスタンス メソッドを指します。

内部では、匿名のメソッド/式はSystem.MulticastDelegate、コード内の派生インスタンスによって表されます。このクラスの Equals メソッドを見ると、2 つの重要な詳細に気付くでしょう。

  • シールされているため、派生デリゲートが equals の動作を変更する方法はありません
  • Equals メソッドの一部は、オブジェクトの参照比較を行います

これにより、異なるクロージャにアタッチされた 2 つのラムダ式を等しいものとして比較することができなくなります。

于 2009-09-14T17:41:36.737 に答える
1

そんなことをしなければならない状況は考えられません。デリゲートを比較する必要がある場合は、常に名前付きデリゲートを使用します。それ以外の場合は、次のようなことが可能です。

MyObject.MyEvent += delegate { return x + y; };

MyObject.MyEvent -= delegate { return x + y; };

この例は問題を示すのには適していませんが、これを許可すると、これが許可されないことを期待して設計された既存のコードが壊れる可能性があると想像できます。

これも悪い考えにする内部実装の詳細があると確信していますが、匿名メソッドが内部でどのように実装されているかは正確にはわかりません。

于 2009-09-14T17:43:10.920 に答える
0

そうしないと匿名のメソッドが混同されるため、この動作は理にかなっています(同じ名前の場合、同じ本文が与えられます)。

コードを次のように変更できます。

static void Main(){   
    bool same = CreateDelegate(1) == CreateDelegate(1);
}

static Action<int> action = (x) => { int z = x; };

private static Action<int> CreateDelegate(int x){
    return action;
}

または、できれば、それを使用するのは悪い方法です(さらに、結果を比較していて、Actionには戻り値がありません...値を返したい場合はFunc <...>を使用してください):

static void Main(){
    var action1 = action;
    var action2 = action;
    bool same = action1 == action2;  // TRUE, of course
}

static Action<int> action = (x) => { int z = x; };
于 2009-09-14T20:04:41.307 に答える