5

序文:私はここでシナリオを非常に正確に説明しようとしています。TL;DRバージョンは'ラムダがインスタンスメソッドまたはクロージャにコンパイルされるかどうかをどのように判断するか' ...

WPFプロジェクトでMvvmLightを使用していますが、そのライブラリは最近、WeakReferenceに渡されるアクションを保持するためにインスタンスを使用するように変更されましたRelayCommandWeakReferenceしたがって、事実上、を保持しているオブジェクトがどこかにありますAction<T>

現在、最新バージョンにアップグレードしてから、一部のコマンドが機能しなくなりました。そして、次のようなコードがありました。

ctor(Guid token)
{
    Command = new RelayCommand(x => Messenger.Default.Send(x, token));
}

これにより、クロージャー(正しい用語を使用していない場合は修正してください)クラスが生成されました-次のようになります:

[CompilerGenerated]
private sealed class <>c__DisplayClass4
{
    public object token;

    public void <.ctor>b__0(ReportType x)
    {
        Messenger.Default.Send<ReportTypeSelected>(new ReportTypeSelected(X), this.token); 
    }
}

アクションはインスタンス内に格納されRelayCommand、インスタンスメソッドにコンパイルされた場合でもクロージャにコンパイルされた場合でも(つまり、「<> DisplayClass」構文を使用して)存続するため、これは以前は正常に機能していました。

ただし、現在はで保持されているため、WeakReference指定されたラムダがインスタンスメソッドにコンパイルされている場合にのみコードが機能します。これは、クロージャクラスがインスタンス化され、渡され、RelayCommand事実上即座にガベージコレクションされるためです。つまり、コマンドが使用されるようになったときに、実行するアクションがありませんでした。したがって、上記のコードを変更する必要があります。これを次のように変更すると、たとえば次のようになります。

Guid _token;
ctor(Guid token)
{
    _token = token;
    Command = new RelayCommand(x => Messenger.Default.Send(x, _token));
}

これにより、コンパイルされたコードは次のようなメンバーになります。

[CompilerGenerated]
private void <.ctor>b__0(ReportType x)
{
    Messenger.Default.Send<ReportTypeSelected>(new ReportTypeSelected(X), this._token);
}

これで上記はすべて問題ありません。以前は機能しなかった理由と、変更すると機能するようになった理由を理解しています。しかし、私が残しているのは、私が今書いているコードは、私が知らないコンパイラーの決定に基づいて、スタイル的に異なっている必要があることを意味します。

だから、私の質問は-これはすべての状況で文書化された動作ですか-それともコンパイラの将来の実装に基づいて動作が変わる可能性がありますか?ラムダを使用することを忘れて、常にインスタンスメソッドを?に渡す必要がありますRelayCommandか?または、アクションが常にインスタンスメンバーにキャッシュされるという規則が必要です。

Action<ReportTypeSelected> _commandAction;
ctor(Guid token)
{
    _commandAction = x => Messenger.Default.Send(x, token);
    Command = new RelayCommand(_commandAction);
}

どんな背景の読書ポインターもありがたいことに受け入れられます!

4

2 に答える 2

3

最終的に新しいクラスになるか、現在のクラスのインスタンスメソッドになるかは、信頼 できない実装の詳細です。

C#仕様の7.15.2章(私の強調):

ラムダ式またはanonymous-method-expressionの評価と呼び出し以外の方法で無名関数のブロックを実行する方法があるかどうかは、明示的に指定されていません。特に、コンパイラーは、1つ以上の名前付きメソッドまたはタイプを合成することによって無名関数を実装することを選択できます。

->メソッドをまったく生成しないという事実も明記されていません。

状況を考えると、匿名メソッドではなく名前付きメソッドを使用します。それが不可能な場合は、コマンドを登録するメソッドから変数にアクセスする必要があるため、最後に示したコードを使用する必要があります。


RelayCommand私の意見では、使用するように変更するという決定WeakReferenceは貧弱なものでした。それはそれが解決したよりもはるかに多くの問題を引き起こしました。

于 2012-12-10T12:20:28.363 に答える
2

ラムダが自由変数(別名キャプチャ)を参照するとすぐに、それらを参照(および/または割り当てる)するための共通の場所(別名ストレージクラス/クロージャ)が必要になるため、これが発生します。

読者の演習は、これらのストレージクラスが単に静的ではない理由を特定することです。

于 2012-12-10T12:22:42.957 に答える