11

匿名メソッドの良い点の 1 つは、呼び出しコンテキストでローカルな変数を使用できることです。これが出力パラメーターと関数の結果に対して機能しない理由はありますか?

function ReturnTwoStrings (out Str1 : String) : String;
begin
  ExecuteProcedure (procedure
                    begin
                      Str1 := 'First String';
                      Result := 'Second String';
                    end);
end;

もちろん、非常に人工的な例ですが、これが役立つ状況に遭遇しました。

これをコンパイルしようとすると、コンパイラは「シンボルをキャプチャできません」と不平を言います。また、これを実行しようとすると、一度内部エラーが発生しました。

編集次のような通常のパラメーターで機能することに気付きました

... (List : TList)

それは他のケースと同じくらい問題ではありませんか?匿名メソッドが実行されるたびに、参照が生きているオブジェクトを指していることを誰が保証しますか?

4

4 に答える 4

21

この操作の安全性を静的に検証できないため、Var および out パラメータと Result 変数をキャプチャできません。Result 変数が文字列やインターフェイスなどのマネージド型の場合、ストレージは呼び出し元によって実際に割り当てられ、このストレージへの参照が暗黙的なパラメーターとして渡されます。つまり、Result 変数は、その型によって異なりますが、out パラメーターのようなものです。

ジョンが言及した理由により、安全性は確認できません。匿名メソッドによって作成されたクロージャーは、それが作成されたメソッドのアクティブ化よりも長く存続する可能性があり、同様に、それが作成されたメソッドを呼び出したメソッドのアクティブ化よりも長く存続する可能性があります。したがって、キャプチャされた var または out パラメーターまたは Result 変数は孤立する可能性があり、将来クロージャー内からそれらに書き込みを行うと、スタックが破損する可能性があります。

もちろん、Delphi は管理された環境では動作せず、C# などと同じ安全上の制限もありません。その言語は、あなたがやりたいことをさせてくれます。ただし、問題が発生した状況では、バグを診断するのが難しくなります。悪い動作は、目に見える直接的な原因のない値を変更するルーチンのローカル変数として現れます。メソッド参照が別のスレッドから呼び出された場合はさらに悪化します。

これをデバッグするのはかなり難しいでしょう。スタックは頻繁に変更されるため、ハードウェア メモリ ブレークポイントでさえ、比較的貧弱なツールです。別のブレークポイントにヒットしたとき (たとえば、メソッドのエントリ時) に、条件付きでハードウェア メモリ ブレークポイントをオンにする必要があります。Delphi デバッガーはこれを行うことができますが、ほとんどの人はこの手法について知らないのではないかと思います。

更新:あなたの質問への追加に関して、インスタンス参照を値で渡すセマンティクスは、クロージャーを含むメソッド (および paramete0 とクロージャーを含まないメソッドをキャプチャする) の間でほとんど異なります。どちらのメソッドもへの参照を保持する場合があります値によって渡される引数; パラメータをキャプチャしないメソッドは、参照をリストに追加するか、プライベート フィールドに格納するだけです。

呼び出し元の期待が異なるため、参照によって渡されるパラメーターでは状況が異なります。これを行うプログラマー:

procedure GetSomeString(out s: string);
// ...
GetSomeString(s);

GetSomeString がs渡された変数への参照を保持しているとしたら、非常に驚​​くでしょう。

procedure AddObject(obj: TObject);
// ...
AddObject(TObject.Create);

が参照を保持していることは驚くべきことではありませんAddObject。名前そのものが、ステートフル ストアにパラメーターを追加していることを暗示しているからです。そのステートフル ストアがクロージャの形式であるかどうかは、AddObjectメソッドの実装の詳細です。

于 2009-04-29T09:15:57.220 に答える
6

問題は、Str1 変数が ReturnTwoStrings によって「所有」されていないため、匿名メソッドがそれをキャプチャできないことです。

それをキャプチャできない理由は、コンパイラが最終的な所有者 (ReturnTwoStrings を呼び出すためのコール スタックのどこか) を認識していないため、どこからキャプチャするかを判断できないためです。

編集: ( Smasherのコメントの後に追加)

匿名メソッドの核心は、変数 (値ではなく) をキャプチャすることです。

Allen Bauer (CodeGear) は、彼のブログで変数のキャプチャーについてもう少し説明しています。

あなたの問題を回避することについてのC# の質問もあります。

于 2009-04-29T06:47:13.007 に答える
4

out パラメーターと戻り値は、関数が戻った後は無関係です。匿名メソッドをキャプチャして後で実行した場合、匿名メソッドはどのように動作すると予想されますか? (特に、匿名メソッドを使用してデリゲートを作成しても実行しない場合、out パラメーターと戻り値は、関数が返されるまでに設定されません。)

Out パラメーターは特に困難です。後でデリゲートを呼び出すときには、out パラメーターのエイリアスが存在していない可能性さえある変数です。たとえば、out パラメーターをキャプチャして匿名メソッドを返すことができたが、out パラメーターは呼び出し元の関数のローカル変数であり、スタック上にあるとします。呼び出し元のメソッドがデリゲートをどこかに保存した後 (またはそれを返した後) に戻った場合、デリゲートが最後に呼び出されたときに何が起こるでしょうか? out パラメータの値が設定されたとき、どこに書き込みますか?

于 2009-04-29T06:44:40.843 に答える
1

あなたのEDITがあなたの質問を本当に違うものにするので、私はこれを別の答えに入れています。

クライアントに連絡するのが少し急いでいるので、おそらく後でこの回答を拡張します。

あなたの編集は、値の型、参照型、および var、out、const の影響、およびパラメーター マーキングなしについて再考する必要があることを示しています。

最初に値型のことをしましょう。

値型の値はスタック上に存在し、コピーオン割り当ての動作をします。(後でその例を含めようとします)。

パラメータ マーキングがない場合、メソッド (プロシージャまたは関数) に渡される実際の値は、メソッド内のそのパラメータのローカル値にコピーされます。したがって、このメソッドは渡された値ではなく、コピーに対して動作します。

out、var、または const がある場合、コピーは行われません。メソッドは、渡された実際の値を参照します。var の場合は実際の値を変更できますが、const の場合は変更できません。out の場合、実際の値を読み取ることはできませんが、実際の値を書き込むことはできます。

参照型の値はヒープ上に存在するため、out、var、const、またはパラメーター マーキングがない場合はほとんど問題になりません。何かを変更すると、ヒープ上の値が変更されます。

参照型の場合、パラメーター マーキングがない場合でもコピーを取得できますが、それはヒープ上の値を指している参照のコピーです。

ここで、無名メソッドが複雑になります。変数キャプチャを行います。(バリーはおそらくこれをもっとうまく説明できますが、試してみます) あなたの編集されたケースでは、匿名メソッドはリストのローカル コピーをキャプチャします。匿名メソッドはそのローカル コピーで機能し、コンパイラの観点からはすべてが適切です。

ただし、編集の要点は、「通常のパラメーターで機能する」と「匿名メソッドが実行されるたびに参照が生きているオブジェクトを指していることを保証する」の組み合わせです。

これは、匿名メソッドを使用するかどうかに関係なく、常に参照パラメーターの問題です。

たとえば、これ:

procedure TMyClass.AddObject(Value: TObject);
begin
  FValue := Value;
end;

procedure TMyClass.DoSomething();
begin
  ShowMessage(FValue.ToString());
end;

誰かが DoSomething を呼び出したときに、FValue が指しているインスタンスがまだ存在することを誰が保証しますか? 答えは、FValue のインスタンスが終了したときに DoSomething を呼び出さないことで、これを自分で保証する必要があるということです。同じことが編集にも当てはまります。基になるインスタンスが終了したときに匿名メソッドを呼び出すべきではありません。

これは、参照カウントまたはガベージ コレクション ソリューションによって生活が楽になる領域の 1 つです。そこでは、インスタンスへの最後の参照がなくなるまで、インスタンスは存続します (これにより、インスタンスが当初の予想よりも長く存続する可能性があります!)。

したがって、編集により、質問は実際には匿名メソッドから、参照型パラメーターと一般的なライフタイム管理を使用することの意味に変わります。

うまくいけば、私の答えがあなたがその分野に進むのに役立つことを願っています。

--jeroen

于 2009-05-01T08:22:43.930 に答える