2013-02-11 12 views
7

Ho un metodo di estensione per sottoscrivere un evento PropertyChanged di un oggetto che implementa INotifyPropertyChanged.Annullamento dell'iscrizione dal gestore di eventi anonimi all'interno di un metodo statico (metodo di estensione)

Mi piacerebbe che l'evento si accenda solo una volta. Non di più.

Questo è il mio metodo.

public static void OnPropertyChanged<T>(this INotifyPropertyChanged target, string propertyName, Action action) 
{ 
    if (target == null) 
    { 
     return; 
    } 

    PropertyChangedEventHandler handler = (obj, e) => 
    { 

     if (propertyName == e.PropertyName) 
     { 
      action(); 
     } 

    }; 


    target.PropertyChanged -= handler; 
    target.PropertyChanged += handler; 

} 

Ma non funziona. Non riesco a rimuovere il gestore eventi in modo che l'evento venga attivato ogni volta che chiamo questo metodo.

Ho provato un approccio diverso. Invece di usare metodi annonymous, qualcosa di più tradizionale, in questo modo:

public static void OnPropertyChanged<T>(this INotifyPropertyChanged target, string propertyName, Action action) 
{ 
    if (target == null) 
    { 
     return; 
    } 

    target.PropertyChanged -= target_PropertyChanged; 
    target.PropertyChanged += target_PropertyChanged; 

} 

static void target_PropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     //do stuff here 
    } 

E funziona bene. L'evento si attiva solo una volta, ma ho anche bisogno del parametro Action. Non posso usarlo con questo approccio.

Qualsiasi soluzione alternativa o approccio diverso per risolvere questo problema? Esiste qualcosa di strano con metodi anonimi all'interno di metodi statici?

Grazie in anticipo.

risposta

3

Questa è una limitazione dell'utilizzo di metodi anonimi come gestori di eventi. Non possono essere rimossi come si farebbe con un metodo normale (che tecnicamente è un'istanza delegata creata automaticamente tramite una conversione del gruppo metodo) perché i metodi anonimi vengono compilati in una classe contenitore generata dal compilatore e ogni volta viene creata una nuova istanza della classe.

Per preservare il parametro action è possibile creare una classe contenitore che includa il delegato per il gestore eventi all'interno. La classe può essere dichiarata privata all'interno dell'altra classe con cui stai lavorando o resa interna, magari nello spazio dei nomi "Helpers". Sarebbe simile a questa:

class DelegateContainer 
{ 
    public DelegateContainer(Action theAction, string propName) 
    { 
     TheAction = theAction; 
     PopertyName = propName; 
    } 

    public Action TheAction { get; private set; } 
    public string PropertyName { get; private set; } 

    public void PropertyChangedHandler(object sender, PropertyChangedEventArgs e) 
    { 
     if(PropertyName == e.PropertyName) 
      TheAction(); 
    } 
} 

Poi, creare e memorizzare il riferimento al contenitore della tua classe. Si potrebbe creare un membro statico currentContainer e quindi impostare il gestore in questo modo:

private static DelegateContainer currentContainer; 

public static void OnPropertyChanged<T>(this INotifyPropertyChanged target, string propertyName, Action action) 
{ 
    if (target == null) 
    { 
     return; 
    } 

    if(currentContainer != null)   
     target.PropertyChanged -= currentContainer.PropertyChangedHandler; 

    currentContainer = new DelegateContainer(action, propertyName); 
    target.PropertyChanged += currentContainer.PropertyChangedHandler; 
} 
+0

+1 per l'idea di una classe contenitore, ma penso che questo sia inutilmente complicato: un 'Dictionary ' può contenere un delegato per nome proprietà. (Sì, questo è un diverso tipo di classe contenitore.) – hvd

+0

Sì, volevo solo presentare una soluzione generica. Quello che stavo implicitamente cercando di trasmettere è che i metodi anonimi con chiusura generano una classe contenitore dietro le quinte che contiene riferimenti alle variabili catturate. Quindi in sostanza è lo stesso tipo di comportamento, ma lo faresti esplicitamente .. –

+0

Grazie mille. Funziona!!! Il 'PropertyChangedEventHandler' si attiva solo una volta. – Nadya

0

Tecnicamente, non è lo stesso metodo anonimo che si sta tentando di annullare l'iscrizione. .NET crea una nuova istanza di quel metodo ogni volta che viene chiamato il tuo OnPropertyChanged. Ecco perché la disiscrizione non funzionerà.

+0

Non è necessariamente solo tecnicamente un metodo diverso. Se diverse istanze 'stringa' dello stesso' propertyName' capita di essere usate per qualsiasi motivo, * deve * essere un metodo diverso. – hvd

+0

@hvd good point – Anri

3

È possibile ottenere il primo esempio di lavoro se si annulla la sottoscrizione da entro il gestore di eventi stesso.

public static void OnPropertyChanged<T>(this INotifyPropertyChanged target, string propertyName, Action action) 
{ 
    if (target == null) 
    { 
     return; 
    } 

    // Declare the handler first, in order to create 
    // a concrete reference that you can use from within 
    // the delegate 
    PropertyChangedEventHandler handler = null; 
    handler = (obj, e) => 
    { 
     if (propertyName == e.PropertyName) 
     { 
      obj.PropertyChanged -= handler; //un-register yourself 
      action(); 
     } 

    }; 
    target.PropertyChanged += handler; 
} 

Il codice di cui sopra serve come "uno e fatto" gestore di eventi. È possibile registrare un numero illimitato di questi e ognuno verrà eseguito una sola volta prima di annullare la registrazione.

Ricordare che è possibile eseguire uno di questi gestori più volte, se si genera l'evento su più thread in rapida successione. Per evitare ciò, potrebbe essere necessario creare istanze di oggetti di mappatura statici Dictionary(T,T) su "blocca oggetti" e aggiungere un po 'di codice sentry per garantire che un gestore venga eseguito una sola volta. Quelle specifiche di implementazione sembrano essere un po 'al di fuori della portata della tua domanda come attualmente scritto, comunque.

+0

Questo è un comportamento diverso. Questo è solo lasciando che sia licenziato una volta, mentre il codice dell'OP stava tentando di assicurare che ci sia sempre un solo gestore (ma può essere licenziato più volte). – Servy

+1

Oh, e nota che se questo evento viene generato da più thread in un breve intervallo di tempo è possibile che venga eseguito più volte. – Servy

+0

Tutti i punti positivi. Può essere richiesto un dizionario statico di oggetti-> gestori, per limitare ogni istanza di un oggetto a un singolo gestore di eventi. – BTownTKD

Problemi correlati