41

渡された整数をインクリメントするアクションを作成できる関数を作成しようとしていますが、最初の試みで「匿名メソッド本体内でrefまたはoutパラメーターを使用できません」というエラーが表示されます。

public static class IntEx {
    public static Action CreateIncrementer(ref int reference) {
        return () => {
            reference += 1;
        };
    }
}

コンパイラがこれを好まない理由は理解していますが、それにもかかわらず、任意の整数を指すことができる素敵なインクリメンタ ファクトリを提供する適切な方法が必要です。これを行う唯一の方法は、次のようなものです。

public static class IntEx {
    public static Action CreateIncrementer(Func<int> getter, Action<int> setter) {
        return () => setter(getter() + 1);
    }
}

しかしもちろん、呼び出し元が使用するのは面倒です。参照を渡すだけでなく、呼び出し元が 2 つのラムダを作成する必要があります。この機能を提供するためのより適切な方法はありますか、それとも 2 つのラムダ オプションを使用する必要がありますか?

4

3 に答える 3

32

さて、安全でないコンテキストでは、ポインターを使用して実際可能であることがわかりました。

public static class IntEx {
    unsafe public static Action CreateIncrementer(int* reference) {
        return () => {
            *reference += 1;
        };
    }
}

ただし、次のように、ガベージ コレクターはガベージ コレクション中に参照を移動することで、これに大混乱をもたらす可能性があります。

class Program {
    static void Main() {
        new Program().Run();
        Console.ReadLine();
    }

    int _i = 0;
    public unsafe void Run() {
        Action incr;
        fixed (int* p_i = &_i) {
            incr = IntEx.CreateIncrementer(p_i);
        }
        incr();
        Console.WriteLine(_i); // Yay, incremented to 1!
        GC.Collect();
        incr();
        Console.WriteLine(_i); // Uh-oh, still 1!
    }
}

変数をメモリ内の特定の場所に固定することで、この問題を回避できます。これは、コンストラクターに次を追加することで実行できます。

    public Program() {
        GCHandle.Alloc(_i, GCHandleType.Pinned);
    }

これにより、ガベージ コレクターがオブジェクトを移動できなくなります。まさに私たちが探しているものです。ただし、デストラクタを追加してピンを解放する必要があり、オブジェクトの存続期間全体でメモリが断片化されます。それほど簡単ではありません。これは、物事が動き回らず、リソース管理が当然のC++ではより理にかなっていますが、すべてが自動化されるはずのC#ではそれほどではありません。

話の教訓は、そのメンバー int を参照型でラップするだけで済むように見えます。

(そして、はい、それは質問をする前に私が働いていた方法ですが、すべての Reference<int> メンバー変数を取り除き、通常の int を使用する方法があるかどうかを理解しようとしていました。まあ。 )

于 2010-11-21T04:14:15.610 に答える
25

これは不可能です。

コンパイラは、匿名メソッドで使用されるすべてのローカル変数とパラメーターを、自動生成されたクロージャー クラスのフィールドに変換します。

refCLR では、型をフィールドに格納すること はできません。

たとえば、値の型をそのようなrefパラメーターとしてローカル変数に渡すと、値の有効期間はスタック フレームを超えて延長されます。

于 2010-11-21T02:30:18.360 に答える
2

永続化を防止するメカニズムを使用して変数参照を作成できるようにすることは、ランタイムにとって便利な機能であった可能性があります。このような機能により、インデクサーが配列のように動作できるようになります (たとえば、「myDictionary[5].X = 9;」を介して Dictionary<Int32, Point> にアクセスできます)。そのような参照を他のタイプのオブジェクトにダウンキャストしたり、フィールドとして使用したり、参照によって渡したりすることができなかった場合、そのような機能は安全に提供できたと思います (そのような参照を格納できる場所は、参照の前に範囲外になるため)それ自体はそうするでしょう)。残念ながら、CLR にはそのような機能はありません。

あなたが求めているものを実装するには、クロージャー内で参照パラメーターを使用する関数の呼び出し元が、そのような関数に渡したい変数をクロージャー内でラップする必要があります。パラメーターがそのような方法で使用されることを示す特別な宣言があった場合、コンパイラーが必要な動作を実装することが実用的である可能性があります。たぶん.net 5.0コンパイラで、それがどれほど役立つかはわかりません.

ところで、私の理解では、Java のクロージャーは値によるセマンティクスを使用しますが、.net のクロージャーは参照によるものです。参照によるセマンティクスの時折の使用は理解できますが、デフォルトで参照を使用することは、VB6 までの VB バージョンでデフォルトの参照によるパラメータ受け渡しセマンティクスを使用するのと同様に、疑わしい決定のようです。関数を呼び出すデリゲートを作成するときに変数の値をキャプチャする場合 (たとえば、デリゲートの作成時に X の値を使用してデリゲートに MyFunction(X) を呼び出す場合)、ラムダを使用することをお勧めします。または、単純にデリゲート ファクトリを使用し、Lambda 式を気にしない方がよいでしょうか。

于 2010-11-21T06:07:55.893 に答える