2015-09-15 57 views
4

如果在一個XAML文件,我結合從下面的類一個按鈕「命令」,然後單擊按鈕不會導致執行DOIT:RelayCommand參數

class Thing() 
{ 
    public Thing(Foo p1) 
    { 
    Command = new RelayCommand(() => DoIt(p1)); 
    } 

    private DoIt(Foo p) 
    { 
    p.DoSomething(); 
    } 

    public ICommand Command { get; private set; } 
} 

然而,

class Thing() 
{ 
    private Foo field; 
    public Thing(Foo p1) 
    { 
    field = p1; 
    Command = new RelayCommand(() => DoIt(field)); 
    } 

    private DoIt(Foo p) 
    { 
    p.DoSomething(); 
    } 

    public ICommand Command { get; private set; } 
} 

爲什麼前者失敗,但後者按預期方式工作:如果我初始化從P1的一個字段,字段作爲參數傳遞給方法調用的拉姆達裏面它的工作?

也許相關:How do closures work behind the scenes? (C#)

編輯:爲了澄清,下面也會爲我工作。然而,我仍然想知道爲什麼第二個例子能夠達到我的預期,但第一個例子沒有。

class Thing() 
{ 
    private Foo field; 
    public Thing(Foo p1) 
    { 
    field = p1; 
    Command = new RelayCommand(DoIt); 
    //Command = new RelayCommand(() => DoIt()); Equivalent? 
    } 

    private DoIt() 
    { 
    field.DoSomething(); 
    } 

    public ICommand Command { get; private set; } 
} 

回答

2

這是一個古老的問題,但我最近偶然發現了這個話題,值得回答。

這種奇怪行爲的原因來自於RelayCommand的MVVM Light實現。執行和未執行處理程序在中繼命令中存儲爲WeakAction _executeWeakFunc<bool> _canExecuteWeakAction嘗試在由於某種原因仍然引用該UI的命令時允許GC清除視圖模型。

跳過一些細節,底線是:將viewmodel方法指定爲處理程序效果很好,因爲只要viewmodel保持活動狀態,WeakAction就會保持活動狀態。對於動態創建的Action,情況是不同的。如果該動作的唯一引用位於RelayCommand之內,則僅存在較弱的參考,並且GC可以隨時收集該動作,從而將整個RelayCommand變成死磚。

好的,詳細的時間。 WeakAction的實現並不是盲目地存儲對動作的弱引用 - 這會導致許多消失的引用。相反,存儲弱Delegate.Target參考和Delegate.MethodInfo的組合。對於靜態方法,該方法將通過強引用進行存儲。現在

,這導致三類拉姆達:

  1. 靜態方法:() => I_dont_access_anything_nonstatic()將被存儲爲一個強引用
  2. 閉合上成員變量:() => DoIt(field)關閉方法將在視圖模型類創建,動作目標是視圖模型,並且只要視圖模型保持活動狀態就會保持活動狀態。
  3. 關閉本地變量:() => DoIt(p1)閉包將創建一個單獨的類實例來存儲捕獲的變量。這個單獨的實例將行動目標,並不會有任何強烈的參考吧 - GC在某些時候清理

重要:據我所知,這種行爲可能與羅斯林改變: Delegate caching behavior changes in Roslyn因此,有情況(2)的今天工作代碼有可能變成與Roslyn無關的代碼。但是,我沒有測試這個假設,它可能完全不同。

0

您的問題是,調用該方法DOIT裏面由LAMDA表達式創建另一個匿名方法。你表達

() => DoIt(p1); 

創建無參數的匿名方法(視爲沒有在第一大括號提供的變量)。

我會建議你使用從MVVM光通用構造函數用於創建命令:

class Thing 
{ 
    public Thing() 
    { 
     Command = new GalaSoft.MvvmLight.Command.RelayCommand<bool>(DoIt); 
    } 

    private void DoIt(bool p) 
    { 
     p.DoSomething(p); 
    } 

    public System.Windows.Input.ICommand Command { get; private set; } 
} 

然後,只需將按鈕綁定到「命令」。

+0

我不想接受來自綁定的參數。也就是說,我希望Thing被賦予一個構造函數參數,並且我希望該參數的值在DoIt中使用。我可以使DoIt無參數,將構造函數參數p1賦值給一個字段,並使用DoIt中的字段(而不是將該字段的值作爲參數傳遞給DoIt),但是我想明確地知道這裏發生了什麼。 – theguy

+0

@theguy我看到了。問題是創建lamda表達式會創建一個匿名的內部方法,然後調用它。你可以看到它: 'public Thing(Foo p1) { Command = new RelayCommand(AnonymousMeth()); } 私人空白AnonymousMeth() { DoIt(p1); }' 因此lamda表達式生成的方法不知道如何處理p1。您可以(正確描述)通過使用專用字段來避免此問題。 – Alexx01