2010-11-21 13 views
39

整数を何回もインクリメントするアクションを作成できる関数を作成しようとしていますが、最初の試みでは "refを使用できません。 「匿名メソッド本体の中のoutパラメータ」を参照してください。C#匿名メソッド本体の内部で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ラムダオプションを使用するだけですか?

+2

これは簡単な例ですか? x ++を使わないのはなぜですか?このクラスの別のクラスをインクリメントする理由は? – Gishu

+2

@ Gishuはいこれは単純な例です。大規模なユースケースは説明するのが難しいですが、すべて値型の操作を実行できるActionファクトリを作成することになります。 –

答えて

24

これはできません。

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

CLRでは、フィールドにフィールドタイプを格納することはできません。

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

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

しかし、ガベージコレクタは、ガベージコレクションの中に、あなたの参照を移動することで、これに大混乱をもたらすことができますよう、:

30

さて、私は危険な状況であれば、それは実際には、ポインタをことが可能であることを発見しました次のようになります。

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を参照型にラップし、それを使って処理するだけです。

(それは私が質問をする前に作業していた方法ですが、すべての私の参照を取り除く方法があるかどうかを調べることでした。参照番号<int>メンバー変数。ああ。)

+4

+1安全でないコンテキストを使用する管理対象モードの面白い回避策です。 –

+0

GCHandle.Allocの必要はありません。固定されたステートメントの中括弧を拡張するだけです。 –

+0

@ Mr.TAそれはGCが物事を混乱させる可能性があることを示す単なる例でした。 incr'関数の価値ある使い方は、おそらくそれを作成するメソッドから返すか、それを永続オブジェクトにメンバ変数として追加します。メンバ変数は明らかに「固定」スコープのままです。 –

2

変数の永続性を妨げるメカニズムで変数参照を作成できるようにすることは、実行時にとって便利な機能でした。このような機能により、インデクサーは配列のように振る舞うことができました(例えば、辞書< Int32、Point>は "myDictionary [5] .X = 9;"でアクセスできます)。私は、そのようなリファレンスが他のタイプのオブジェクトにダウンキャストされたり、フィールドとして使用されたり、参照自体によって渡されたりすることができない場合、そのような機能が安全に提供されていると思います。それ自体)。残念ながら、CLRはそのような機能を提供していません。

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

私の理解では、Javaでの値のセマンティクスのクロージャーがありますが、.netのものは参照によるものです。私は時々参照によるセマンティクスの使用を理解することができますが、参照を既定で使用することは、VB6からVB6までの既定の参照によるパラメータ渡しセマンティクスの使用に類似しています。関数を呼び出すためにデリゲートを作成するときに変数の値を取得する場合(デリゲートが作成されたときにデリゲートがXの値を使用してMyFunction(X)を呼び出すようにしたい場合など)は、ラムダ特別なファクトリを使用し、ラムダ式を気にしない方がよいでしょう。

+1

ええ、エリック・リッパートはそれについてのブログを書いています。 http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx。私は実際にこの動作に依存するコードを持っています。例えば、framecountメンバ変数を必要とせずに、OpenGLループのためのフレームカウントを保持します。私はどちらのセマンティックでも良いC++仕様が好きです。 –

+1

@Dax:参照渡しのセマンティクスが必要な場合があります。しかし、同じことがパラメータの受け渡しにも当てはまります。これは、参照渡しがデフォルトであることを意味するものではありません。私は、クロージャ変数のさまざまな組み合わせ(いくつかのGCの問題を避ける)が必要な匿名メソッドに対して、異なるクラスを作成できるように、参照要素クロージャ変数を単一要素配列に置き換えることの長所と短所を考えました。 – supercat

関連する問題