2009-09-07 142 views
17

我有以下代碼:C# - 匿名函數和事件處理

public List<IWFResourceInstance> FindStepsByType(IWFResource res) 
{ 
    List<IWFResourceInstance> retval = new List<IWFResourceInstance>(); 
    this.FoundStep += delegate(object sender, WalkerStepEventArgs e) 
         { 
         if (e.Step.ResourceType == res) retval.Add(e.Step); 
         }; 
    this.Start(); 
    return retval; 
} 

通知我如何註冊我的事件成員(FoundStep)到當地就地匿名函數。

我的問題是:當函數'FindStepByType'將結束 - 將匿名函數從事件的委託列表中自動刪除,或者我必須在執行該函數之前手動刪除它? (以及我該怎麼做?)

我希望我的問題很清楚。

回答

34

您的代碼(有些你和其他人已經確定)的幾個問題:

  • 匿名委託無法從活動中移除的編碼。
  • 匿名代理的壽命比調用它的方法的壽命要長,因爲您已將它添加到FoundStep這是這個的成員。
  • 每進入FindStepsByType增加另一個匿名代表FoundStep
  • 匿名委託是一個封閉和有效地延長的RETVAL壽命,所以即使你停止引用代碼中的其他地方RETVAL,它仍然通過匿名委託舉行。

爲了解決這個問題,並且仍然使用匿名委託,將其分配給一個局部變量,然後移除終於塊內的處理程序(必要的情況下,處理程序拋出異常):

public List<IWFResourceInstance> FindStepsByType(IWFResource res) 
    { 
    List<IWFResourceInstance> retval = new List<IWFResourceInstance>(); 
    EventHandler<WalkerStepEventArgs> handler = (sender, e) => 
    { 
     if (e.Step.ResourceType == res) retval.Add(e.Step); 
    }; 

    this.FoundStep += handler; 

    try 
    { 
     this.Start(); 
    } 
    finally 
    { 
     this.FoundStep -= handler; 
    } 

    return retval; 
    } 

用C#7.0或更高版本,你可以用本地函數替換匿名委託,達到同樣的效果:

public List<IWFResourceInstance> FindStepsByType(IWFResource res) 
    { 
     var retval = new List<IWFResourceInstance>(); 

     void Handler(object sender, WalkerStepEventArgs e) 
     { 
      if (e.Step.ResourceType == res) retval.Add(e.Step); 
     } 

     FoundStep += Handler; 

     try 
     { 
      this.Start(); 
     } 
     finally 
     { 
      FoundStep -= Handler; 
     } 

     return retval; 
    } 
5

不,它不會被自動刪除。從這個意義上說,匿名方法和「正常」方法之間沒有區別。如果你願意,你應該手動退訂該事件。

實際上,它會捕獲其他變量(例如在您的示例中爲res)並使它們保持活動狀態(防止垃圾收集器收集它們)。

+0

是不是就像使用謂詞一樣?當我使用謂詞時,我不釋放謂詞委託。 – 2009-09-07 14:02:32

+2

謂詞不保存在任何地方,但在這裏,您正在訂閱一個事件。只要包含該事件的對象處於活動狀態,它就會持有對您的委託的引用,並間接對其變量進行引用。當你通過'.Where(x => x.Hidden)'來傳遞某個方法時,該方法將對它進行處理並將其扔掉(就'Where'方法而言,它只是一個局部變量。這不適用於你的情況。另外,如果'Where'存儲在某個地方,你也應該擔心這一點。 – 2009-09-07 14:05:49

3

使用匿名委託(或lambda表達式)訂閱事件時,不允許您以後輕鬆取消訂閱該事件。事件處理程序永遠不會自動取消訂閱。

如果你看看你的代碼,即使你聲明和訂閱了一個函數中的事件,你訂閱的事件也是在類中,所以一旦訂閱了它,即使在函數退出後,它也會被訂閱。另一個重要的事情是,每次調用這個函數時,它都會再次訂閱該事件。這是完全合法的,因爲事件本質上是多播委託並允許多個訂閱者。 (這可能或可能不是你想要的。)

爲了在退出函數之前取消訂閱委託,您需要將匿名委託存儲在委託變量中,並將該委託添加到事件中。然後,您應該能夠在函數退出之前從該事件中移除該委託。

由於這些原因,如果您稍後必須退訂該活動,建議不要使用匿名代表。請參閱How to: Subscribe to and Unsubscribe from Events (C# Programming Guide)(特別是標題爲「使用匿名方法訂閱事件」的部分)。

5

下面是有關在如何退訂事件方法詭異的方法:

DispatcherTimer _timer = new DispatcherTimer(); 
_timer.Interval = TimeSpan.FromMilliseconds(1000); 
EventHandler handler = null; 

int i = 0; 

_timer.Tick += handler = new EventHandler(delegate(object s, EventArgs ev) 
{ 
    i++; 
    if(i==10) 
     _timer.Tick -= handler; 
}); 

_timer.Start();