質問

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

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

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

これをコンパイルしようとすると、コンパイラは「シンボルをキャプチャできない」というメッセージを出します。また、これを実行しようとしたときに一度内部エラーが発生しました。

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

... (List : TList)

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

役に立ちましたか?

解決

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

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

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

これをデバッグするのはかなり困難です。スタックは頻繁に変更されるため、ハードウェア メモリ ブレークポイントであっても、ツールとしては比較的不十分です。別のブレークポイントに到達したら、条件付きでハードウェア メモリ ブレークポイントをオンにする必要があります (例:メソッド入力時)。Delphi デバッガーはこれを行うことができますが、ほとんどの人はこのテクニックについて知らないと推測します。

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

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

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

GetSomeString が s 渡される変数。一方で:

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

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

他のヒント

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

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

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

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

アレンバウアー(CodeGear)は、変数のキャプチャについてもう少しを説明しています。 彼のブログで

C#の質問があります問題の回避についても同様です。

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

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

編集により質問が大きく異なるため、これを別の回答に入れています。

クライアントにたどり着くのに少し急いでいるので、この回答を後で拡張するでしょう。

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

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

値型の値はスタック上に存在し、割り当て時のコピー動作があります。 (これについては後で例を示します)。

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

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

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

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

これは、匿名メソッドが複雑になる場所です。変数のキャプチャを行います。 (Barryはおそらくこれをさらによく説明できますが、試してみます) 編集したケースでは、匿名メソッドはリストのローカルコピーをキャプチャします。匿名メソッドはそのローカルコピーで動作し、コンパイラの観点からはすべてがダンディです。

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

匿名メソッドを使用するかどうかにかかわらず、それは常に参照パラメーターの問題です。

たとえば、これ:

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

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

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

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

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

うまくいけば、私の答えがあなたがその地域に行くのを助けてくれます。

-jeroen

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top