2009-04-29 9 views
9

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

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

非常に人工的な例ですが、私はこれが役に立つと思われるいくつかの状況に遭遇しました。

コンパイルしようとすると、コンパイラは「シンボルを取得できません」という文句を言います。また、私はこれをしようとしたときに一度内部的なエラーが発生しました。

EDIT私はちょうどそれが

... (List : TList) 

のような通常のパラメータのために働くが、そのように問題のある他の例としてではないことを実現?匿名メソッドが実行されるたびに参照がまだ生きているオブジェクトを指し示していることを誰が保証していますか?

+0

参照されるパラメータの代わりにポインタを使用します。 – MajidTaheri

答えて

20

この操作の安全性を静的に検証できないため、VarとoutパラメータとResult変数は取得できません。 Result変数が文字列やインタフェースなどの管理対象型の場合、実際にはストレージは呼び出し元によって割り当てられ、このストレージへの参照は暗黙のパラメータとして渡されます。言い換えれば、Result型はその型に応じて、outパラメータと似ています。

Jonが述べた理由で安全性を確認することはできません。匿名メソッドによって作成されたクロージャは、作成されたメソッドのアクティブ化よりも長く存続する可能性があり、作成されたメソッドを呼び出したメソッドのアクティブ化よりも長く存続する可能性があります。したがって、取り込まれた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メソッドの実装の詳細です。

+0

なぜスタックについて話しますか?キャプチャされた変数はスタックに格納されず、インタフェースを実装する隠しオブジェクトに格納されます。私。 var M、N:整数。 - 匿名メソッドでNのみが使用されている場合、Mはスタックに行き、Nは隠しオブジェクトのフィールドになります。スタックには表示されません。私は何かを誤解していますか? – Alex

+0

@アレクサンダー:バリーは、それが捕捉され、パラメータと機能の結果が変わることが許されたときに何が起こるかという状況を説明していました。 これは許可されていないため、スタック上書きの状況は発生しません。 –

+0

+1 @Barry:私ができるよりはるかに良い説明。 –

3

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

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

+0

あなたの最初の点について:それは私が使用するすべてのローカル変数に当てはまりますか?そして、第2の点にもかかわらず、それにもかかわらず匿名の方法で関数の結果を生成したいのであればどうしますか?ローカル変数を使用してこれを簡単にエミュレートし、匿名メソッドでそのローカル変数を使用し、それをResult aftwerwardsに割り当てます。 – jpfollenius

+0

私の主張を明確にするために、anonmouyメソッドをデリゲートとして使用すると同じ問題が発生するでしょうか? – jpfollenius

+1

いいえ、メソッド自体のローカル変数には当てはまりません。 DelphiはC#に似ていると仮定して、代わりにヒープに配置することでキャプチャされます。ローカル変数への参照は、実際にはそのスコープ内のすべてのローカル変数の "コンテナ"を経由します。コンパイラは、どの変数がキャプチャされるかを知っているので、メソッド内のローカル変数に対してこれを行うことができます。このメソッドを呼び出して*コード内のローカル変数に対してはできません。 –

6

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

それをキャプチャできないのは、コンパイラが最終的な所有者(呼び出しスタックのどこかでReturnTwoStringsを呼び出している)を知っていないため、どこから取得するのかを決定できないことです。

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

匿名メソッドのコアが、彼らは、変数(ないそれらの値)を取り込むことです。

アレン・バウアー(コードギア)はもう少し説明しますabout variable capturing in his blog

C# question about circumventing your problemもあります。

+0

+1、非常に技術的な理由 – jpfollenius

0

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

私はクライアントに手を差し伸べるのに少し時間がかかるので、この回答を後で延長する予定です。

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

まず、値の種類の処理をしましょう。

値型の値はスタック上に存在し、割り当て時コピー動作を持ちます。 (例をあげておきます)。

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

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

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

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

これは、匿名メソッドが複雑になる場所です。変数の取り込みが行われる場所です。 編集した場合、匿名メソッドはリストのローカルコピーを取得します。匿名のメソッドはそのローカルコピーで動作し、コンパイラの観点からはすべてが大変です。

しかし、あなたの編集の要点は、 'それは正常なパラメータのために働く'と '匿名メソッドが実行されるたびに参照がまだ生きているオブジェクトを指し示していることを保証する'という組み合わせです。

匿名メソッドを使用するかどうかに関係なく、常に参照パラメータに問題があります。例えばこのため

:誰かが、doSomethingの呼び出したときにインスタンスがFValueポイントがまだ存在する場所ということを保証

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

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

? 答えは、FValueへのインスタンスがなくなったときにDoSomethingを呼び出さないようにする必要があります。 編集にも同じことが言えます。基礎となるインスタンスがなくなったときに匿名メソッドを呼び出すべきではありません。

これは、参照カウントまたはガーベッジコレクションのソリューションがより使いやすくなる領域の1つです。インスタンスが最後に参照されるまで生き残ります(インスタンスが元々予想より長く生き残る可能性があります)。 )。

あなたの編集では、匿名メソッドから実際に参照型パラメータとライフタイム管理を使用することの意味に変更されます。

私の答えは、あなたがその地域に行くのを助けてくれることを願っています。

--jeroen