2013-03-13 13 views
12

In C#, qual è il modo migliore per ritardare la gestione di tutti gli eventi noti fino a quando un'entità non è stata completamente modificata? Diciamo, per esempio, che un soggetto - MyEntity - ha la proprietà ID, Nome e Descrizione ...Ritarda la gestione degli eventi fino a quando gli eventi non sono stati attivati ​​

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

Quando si modifica ciascuna di queste proprietà, un evento viene generato per ogni modifica.

A volte, l'ID è l'unica proprietà modificata e talvolta tutte le proprietà vengono modificate. Voglio che i listener registrati dell'evento di modifica attenderanno fino a quando tutte le proprietà modificate nel "batch" non saranno state modificate.

Qual è il modo migliore per realizzare questo?

Nella mia testa, qualcosa di simile al pattern UnitOfWork, dove è possibile racchiudere un'istruzione using attorno alla chiamata al metodo nel livello più alto dello stack di chiamate ma non ha idea di come implementare una cosa del genere ...

Modifica: Come un chiarimento ... Gli ascoltatori sono sparsi attraverso l'applicazione e sono in esecuzione in altri thread. Un altro attore imposta, ad esempio, il nome che deve chiamare la proprietà MyEntity.Name per impostare il valore.

A causa della progettazione, la modifica della proprietà Nome può attivare altre proprietà da modificare, quindi la necessità per gli ascoltatori di sapere che la modifica delle proprietà è stata completata.

+0

dipende molto da altri requisiti, per esempio gli ascoltatori agiscono su singole modifiche di proprietà o elaborano sempre l'entità, indipendentemente dalla quantità e dalla posizione delle modifiche? –

+0

Perché non utilizzare le bandiere? Se i flag 1 e 2 sono impostati, ma 3 non lo è ... non fare nulla. Quando viene attivato l'evento e viene impostato il flag 3, THEN elabora il codice che si desidera eseguire. –

+0

OT: Mi sono reso conto che il mio pensiero originale con l'istruzione "using" proveniva dall'implementazione di log4net in cui log4net.NDC.Push() è chiamato per inviare un messaggio di contesto al contesto corrente del thread ... – ForestC

risposta

4

Solo il codice che esegue le modifiche può sapere quando il suo lotto di modifiche è completo.

Quello che ho fatto con le mie classi simili è quello di fornire i metodi SuspendNotifications() e ResumeNotifications(), che sono chiamati in modo ovvio (ad esempio, sospendere la chiamata prima di apportare una serie di modifiche, richiamare il resume al termine).

Mantengono internamente un contatore che viene incrementato in SuspendNotifications() e decrementato da ResumeNotifications() e se il decremento risulta pari a zero, viene emessa una notifica. L'ho fatto in questo modo perché a volte dovevo modificare alcune proprietà e quindi chiamare un altro metodo che ne modificava di più e che a sua volta chiamava sospensione/ripresa.

(Se curriculum è chiamato troppe volte, ho generato un'eccezione.)

Se più proprietà vengono modificate, la notifica finale non nomina la proprietà di essere cambiato (in quanto non v'è più di uno). Suppongo che potresti accumulare un elenco di proprietà modificate e rilasciarlo come parte della notifica, ma ciò non sembra molto utile.

Si noti inoltre che la sicurezza del thread potrebbe essere o meno un problema per l'utente. Potrebbe essere necessario utilizzare il blocco e/o Interlocked.Increment() ecc.

L'altra cosa è che, naturalmente, si finisce per dover provare/aggirare le chiamate per sospendere/riprendere nel caso ci sia un'eccezione. Puoi evitarlo scrivendo una classe wrapper che implementa IDisposable e le chiamate riprendono nel suo Dispose.

codice potrebbe essere simile a questo:

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

    finally 
    { 
     _entity.ResumeNotifications(); 
    } 
} 

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

[EDIT]

Se si dovesse introdurre un'interfaccia, dire ISuspendableNotifications, si potrebbe scrivere una classe IDisposable wrapper per semplificare le cose.

L'esempio seguente illustra il concetto; L'uso di NotificationSuspender semplifica (in realtà rimuove) la logica try/catch.

Si noti che class Entity non implementa effettivamente sospendere/riprendere o fornire alcuna gestione degli errori; questo è lasciato come esercizio proverbiale per il lettore. :)

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 Il mio approccio preferito . –

+0

Questo sembra un po 'il mio pensiero iniziale di una dichiarazione usando. Immagino di poter aggiungere gli eventi a una coda interna e quindi elaborare la coda quando vengono ripristinate le notifiche. Se così fosse - mi servirebbe qualcosa in coda per far sapere agli ascoltatori quando un batch è completo ... – ForestC

+2

Un buon esempio MSDN di questo modello proviene da Control con [SuspendLayout] (http://msdn.microsoft.com/en-us/library/system.windows.forms.control.suspendlayout.aspx) e [ResumeLayout] (http://msdn.microsoft.com/en-us/library/system.windows.forms.control.resumelayout.aspx) –

0

penso che questo sarà dura, come gli eventi vengono attivati ​​in modo asincrono, ma vengono gestiti in modo sincrono dal thread di esecuzione. Una possibilità sarebbe quella di utilizzare AutoResetEvent o ManualResetEvent e utilizzare il metodo WaitOne per attendere che lo Set lo rilasci.
Probabilmente dovresti usarlo in combinazione con uno Mutex. Ma se lavori solo su un singolo thread, questo non funzionerà.

Vedere here per ManualResetEvent e here per AutoResetEvent.

+0

L'applicazione è multithread. Forse l'opzione migliore sarebbe quella di "raccogliere" tutti gli eventi in un singolo evento genitore (dove gli eventi secondari sono figli)? – ForestC

+0

@Forest Non conosco molto la struttura del tuo programma. Se riesci a combinarlo, potrebbe funzionare. –

0

Supponendo che tutti i tuoi eventi utilizzano la stessa firma:

  1. inizializzare un esempio delegato eventQueue a MyEntity esemplificazione, e un valore int ad esempio 'queueRequiredLength' setter
  2. Ogni struttura aggiunge aggiunge il loro evento alla coda se non già presente, eventQueue += newEvent; invece di spegnere l'evento.
  3. Ogni setter proprietà allora controlla la lunghezza della coda e spara il delegato (vale a dire tutti gli eventi in coda) if(length == queueRequiredLength) {eventQueue();}

(Fuori superiore della mia testa non sono sicuro di come controllare il numero di metodi " in coda "nel delegato, ma nel peggiore dei casi si potrebbe anche solo tenere un contatore e incrementarlo con ogni aggiunta alla coda).

1

Potrei suggerire

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(); 
     } 
    } 

} 
Problemi correlati