2013-03-13 70 views
12

在C#中,延遲處理所有已知事件的最佳方式是什麼,直到一個實體被完全修改爲止? 說,例如,一個實體 - myEntity所 - 具有屬性的ID,名稱和描述...延遲事件處理,直到事件被觸發爲止

public class MyEntity 
    { 
     public Int32 ID { get; set; } 
     public String Name { get; set; } 
     public String Description { get; set; } 
    } 

當修改每個這些屬性的,一個事件被觸發爲每個修改。

有時,ID是修改的唯一屬性,有時會修改所有屬性。我希望修改事件的註冊偵聽器等待,直到修改了「批處理」中修改的所有屬性。

完成此操作的最佳方法是什麼?

在我的腦海中,類似於UnitOfWork-pattern的東西,可以在調用堆棧頂層的方法調用中包裝using語句,但不知道如何實現這樣的事情......

編輯: 作爲一個澄清......聽衆分散在整個應用程序,並在其他線程執行。另一個actor(例如)設置它必須調用MyEntity.Name屬性來設置值的名稱。

由於設計原因,Name屬性的修改可能觸發其他屬性的更改,因此需要偵聽器知道屬性的修改已完成。

+0

很大程度上取決於其他要求,例如聽衆對單個財產的變化採取行動還是他們總是處理實體,而不管修改的數量和位置如何? – 2013-03-13 13:34:06

+0

爲什麼不使用標誌?如果標誌1和2被設置,但3不是...不要做任何事情。當事件觸發並設置了標記3時,THEN會處理您要執行的任何代碼。 – 2013-03-13 13:36:25

+0

OT: 我意識到我最初的想法與「使用」語句來自log4net實現,其中調用log4net.NDC.Push()以將上下文消息推送到當前線程的上下文中... – ForestC 2013-03-13 15:02:57

回答

4

只有執行修改的代碼才能知道其批次更改是否完成。

我對我的類似課程所做的工作是提供SuspendNotifications()ResumeNotifications()方法,這些方法以明顯的方式調用(即在進行一系列更改前調用暫停,完成時調用恢復)。

它們在內部維護一個計數器,該計數器在SuspendNotifications()中遞增,並由ResumeNotifications()遞減,如果遞減結果爲零,則會發出通知。我這樣做是因爲有時候我會修改一些屬性,然後調用另一個修改更多的方法,並且本身會調用暫停/恢復。

(如果簡歷被稱爲太多次,我拋出的異常。)

如果多個屬性被更改,最後通知不名要更改的屬性(因爲有不止一個)。我想你可以累積一個已更改的屬性列表,並將其作爲通知的一部分發布,但這聽起來不太有用。

另請注意,線程安全性可能會或可能不會成爲您的問題。您可能需要使用鎖定和/或Interlocked.Increment()等。

另一件事是,當然最終需要圍繞您的調用try/catch暫停/恢復,以防出現異常。您可以通過編寫實現IDisposable的包裝類來避免這種情況,並在其Dispose中調用resume。

代碼可能是這樣的:

public void DoStuff() 
{ 
    try 
    { 
     _entity.SuspendNotifications(); 
     setProperties(); 
    } 

    finally 
    { 
     _entity.ResumeNotifications(); 
    } 
} 

private setProperties() 
{ 
    _entity.ID = 123454; 
    _entity.Name = "Name"; 
    _entity.Description = "Desc"; 
} 

[編輯]

如果你要引入一個接口,說ISuspendableNotifications,你可以寫一個IDisposable包裝類把事情簡單化。

下面的例子說明了這個概念;使用NotificationSuspender簡化了(實際上刪除了)try/catch邏輯。

請注意,class Entity當然並不實際掛起/恢復或提供任何錯誤處理;這對讀者來說就是衆所周知的。 :)

using System; 

namespace Demo 
{ 
    public interface ISuspendableNotifications 
    { 
     void SuspendNotifications(); 
     void ResumeNotifications(); 
    } 

    public sealed class NotificationSuspender: IDisposable 
    { 
     public NotificationSuspender(ISuspendableNotifications suspendableNotifications) 
     { 
      _suspendableNotifications = suspendableNotifications; 
      _suspendableNotifications.SuspendNotifications(); 
     } 

     public void Dispose() 
     { 
      _suspendableNotifications.ResumeNotifications(); 
     } 

     private readonly ISuspendableNotifications _suspendableNotifications; 
    } 

    public sealed class Entity: ISuspendableNotifications 
    { 
     public int Id { get; set; } 
     public string Name { get; set; } 
     public string Description { get; set; } 

     public void SuspendNotifications() {} 
     public void ResumeNotifications() {} 
    } 

    public static class Program 
    { 
     public static void Main(string[] args) 
     { 
      Entity entity = new Entity(); 

      using (new NotificationSuspender(entity)) 
      { 
       entity.Id = 123454; 
       entity.Name = "Name"; 
       entity.Description = "Desc"; 
      } 
     } 
    } 
} 
+0

+1我的首選方法。 – 2013-03-13 13:51:44

+0

這看起來有點像我對使用語句的初步想法。我想我可以將事件添加到內部隊列,然後在通知恢復時處理隊列。 如果是這樣 - 我需要隊列中的某些東西讓聽衆知道批次何時完成... – ForestC 2013-03-13 14:04:54

+2

這個模式的一個很好的MSDN示例是使用它的[SuspendLayout](http://msdn.microsoft.com/zh-cn/library/system.windows.forms.control.suspendlayout.aspx)和[ResumeLayout]( http://msdn.microsoft.com/en-us/library/system.windows.forms.control.resumelayout.aspx) – 2013-03-13 14:31:24

0

我認爲這將是艱難的,因爲事件是異步觸發的,但是由執行線程同步處理。一種可能性是使用AutoResetEventManualResetEvent並使用WaitOne-方法來等待Set發佈它。
您可能需要將它與Mutex結合使用。但是如果你只在一個線程上工作,這將不起作用。

請參閱here對於ManualResetEventhere對於AutoResetEvent

+0

該應用程序是多線程的。也許最好的選擇是「收集」一個父事件(子事件是兒童)中的所有事件? – ForestC 2013-03-13 14:07:57

+0

@Forest我不太瞭解你的程序的結構。如果你能夠結合它,它可能會工作。 – 2013-03-13 14:11:03

0

假設所有的事件都使用相同的簽名:

  1. 在myEntity所實例初始化一個委託例如eventQueue,和一個int值,例如,「queueRequiredLength」
  2. 每個屬性的setter增加了增加了他們的事件隊列如果不是已經存在,eventQueue += newEvent;,而不是僅僅發射事件。
  3. 每個屬性的setter然後檢查隊列的長度並觸發委託(即所有排隊的事件)if(length == queueRequiredLength) {eventQueue();}

(關我的頭頂,我不知道如何檢查的方法數「排隊「,但最壞的情況下,你也可以保留一個計數器,每增加一個計數器就增加一個計數器)。

1

我可以建議

public class MyEntity 
{ 
    private const int FieldsCount = 3; 

    private Int32 id; 
    private String name; 
    private String description; 

    private HashSet<string> dirty = new HashSet<string>(); 

    public Int32 ID 
    { 
     get { return id; } 
     set 
     { 
      id = value; 
      dirty.Add("id"); 
      GoListeners(); 
     } 
    } 

    //... 

    private void GoListeners() 
    { 
     if (dirty.Count == FieldsCount) 
     { 
      //... 
      dirty.Clear(); 
     } 
    } 

}