2010-09-08 10 views
7

Ci sono stati molti articoli su come utilizzare reflection e LINQ per generare eventi PropertyChanged in modo sicuro, senza utilizzare le stringhe.Gestione della proprietà modificata in modo sicuro per tipo

Ma c'è un modo per consumare gli eventi di proprietà di PropertyChanged in un modo sicuro? Attualmente, sto facendo questo

void model_PropertyChanged(object sender, PropertyChangedEventArgs e) 
{ 
    switch (e.PropertyName) 
    { 
     case "Property1": 
      ... 
     case "Property2": 
      ... 

     ....    
    } 
} 

C'è un modo per evitare di stringhe hard-codifica in un'istruzione switch per gestire le diverse proprietà? Qualche approccio simile a LINQ o riflessione?

risposta

3

Diamo dichiarare un metodo che può trasformare un'espressione lambda in un oggetto di riflessione PropertyInfo (taken from my answer here):

public static PropertyInfo GetProperty<T>(Expression<Func<T>> expr) 
{ 
    var member = expr.Body as MemberExpression; 
    if (member == null) 
     throw new InvalidOperationException("Expression is not a member access expression."); 
    var property = member.Member as PropertyInfo; 
    if (property == null) 
     throw new InvalidOperationException("Member in expression is not a property."); 
    return property; 
} 

E poi lo si può usare per ottenere i nomi delle proprietà:

void model_PropertyChanged(object sender, PropertyChangedEventArgs e) 
{ 
    if (e.PropertyName == GetProperty(() => Property1).Name) 
    { 
     // ... 
    } 
    else if (e.PropertyName == GetProperty(() => Property2).Name) 
    { 
     // ... 
    } 
} 

Purtroppo non è possibile utilizzare un'istruzione switch poiché i nomi di proprietà non sono più costanti in fase di compilazione.

+0

(Anche se, francamente, se si vuole veramente adeguata sicurezza tipo, forse si dovrebbe prendere in considerazione non usando 'PropertyChangedEventArgs' a tutti e invece dichiarando uno proprio che contiene l'oggetto' PropertyInfo' invece di una stringa.) – Timwi

1

Josh Smith's MVVM Foundation include una classe PropertyObserver che esegue ciò che si desidera.

+0

Il suo codice usa ancora le stringhe per passare il nome della proprietà, aggiunge semplicemente una verifica per verificare che sia un nome di proprietà valido: if (TypeDescriptor.GetProperties (this) [propertyName] == null) –

+0

+ 1 per indicare un grande serie di articoli - ottimo codice e spiegazioni ben fatte –

1

Evito l'opzione combinando lo schema di comando e una certa logica di espressione. Incapsula l'azione del caso in un comando. Illustrerò ciò usando una struttura Model View Controller. codice del mondo reale - WinForms, ma è la stessa idea

l'esempio carica un albero in una vista, quando la proprietà Tree è impostata nel modello.

un ICommand personalizzato

void Execute(); 
string PropertyName { get; } 

Comando Calcestruzzo

public TreeChangedCommand(TreeModel model, ISelectTreeView selectTreeView,Expression<Func<object>> propertyExpression) 
    { 
     _model = model; 
     _selectTreeView = selectTreeView; 

     var body = propertyExpression.Body as MemberExpression; 
     _propertyName = body.Member.Name; 

    } 

regolatore costruttore

//handle notify changed event from model 
    _model.PropertyChanged += _model_PropertyChanged; 
    //init commands 
    commands = new List<ICommand>(); 
    commands.Add(new TreeChangedCommand(_model,_mainView,()=>_model.Tree)); 

gestore PropertyChanged

void _model_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) 
{ 
    //find the corresponding command and execute it. (instead of the switch) 
    commands.FirstOrDefault(cmd=>cmd.PropertyName.Equals(e.PropertyName)).Execute(); 
} 
1

Una soluzione recente sono venuto in mente è per incapsulare la logica dell'evento spedizione in una classe dedicata.

la classe ha un metodo pubblico chiamato Handle che ha la stessa firma del delegato PropertyChangedEventHandler significa che può essere sottoscritto al PropertyChanged caso di qualsiasi classe che implementa l'interfaccia INotifyPropertyChanged.

La classe accetta delegati come lo spesso utilizzato DelegateCommand utilizzato dalla maggior parte delle implementazioni WPF, il che significa che può essere utilizzato senza dover creare sottoclassi.

La classe si presenta così:

public class PropertyChangedHandler 
{ 
    private readonly Action<string> handler; 
    private readonly Predicate<string> condition; 
    private readonly IEnumerable<string> properties; 

    public PropertyChangedHandler(Action<string> handler, 
     Predicate<string> condition, IEnumerable<string> properties) 
    { 
     this.handler = handler; 
     this.condition = condition; 
     this.properties = properties; 
    } 

    public void Handle(object sender, PropertyChangedEventArgs e) 
    { 
     string property = e.PropertyName ?? string.Empty; 

     if (this.Observes(property) && this.ShouldHandle(property)) 
     { 
      handler(property); 
     } 
    } 

    private bool ShouldHandle(string property) 
    { 
     return condition == null ? true : condition(property); 
    } 

    private bool Observes(string property) 
    { 
     return string.IsNullOrEmpty(property) ? true : 
      !properties.Any() ? true : properties.Contains(property); 
    } 
} 

È possibile quindi registrare un gestore di eventi cambiato proprietà come questa:

var eventHandler = new PropertyChangedHandler(
    handler: p => { /* event handler logic... */ }, 
    condition: p => { /* determine if handler is invoked... */ }, 
    properties: new string[] { "Foo", "Bar" } 
); 

aViewModel.PropertyChanged += eventHandler.Handle; 

Il PropertyChangedHandler si occupa di verificare la PropertyName del PropertyChangedEventArgs e assicura che handler viene richiamato dalle modifiche alle proprietà corrette.

Si noti che PropertyChangedHandler accetta anche un predicato in modo che il delegato del gestore possa essere inviato condizionatamente. La classe consente inoltre di specificare più proprietà in modo che un singolo gestore possa essere associato a più proprietà contemporaneamente.

Questo può essere facilmente esteso con alcune estensioni metodi per più conveniente di registrazione del gestore, che ti permette di creare il gestore di eventi e sottoscrivere l'evento PropertyChanged in una singola chiamata di metodo e specificare le proprietà usando espressioni invece di stringhe per realizzare qualcosa che assomiglia a questo:

aViewModel.OnPropertyChanged(
    handler: p => handlerMethod(), 
    condition: p => handlerCondition, 
    properties: aViewModel.GetProperties(
     p => p.Foo, 
     p => p.Bar, 
     p => p.Baz 
    ) 
); 

questo è fondamentalmente dicendo che quando uno le proprietà Foo, Bar o Baz cambiamento sarà richiamato handlerMethod se handlerCondition è vero.

Sono previsti sovraccarichi del metodo OnPropertychanged per coprire i diversi requisiti di registrazione degli eventi.

Se, ad esempio, si desidera registrare un gestore che viene chiamato per qualsiasi proprietà evento modificato e viene sempre eseguita si può semplicemente effettuare le seguenti operazioni:

aViewModel.OnPropertyChanged(p => handlerMethod()); 

Se, ad esempio, si desidera registrare un gestore che viene sempre eseguito, ma solo per un singolo specifica modifica della proprietà è possibile effettuare le seguenti operazioni:

aViewModel.OnPropertyChanged(
    handler: p => handlerMethod(), 
    properties: aViewModel.GetProperties(p => p.Foo) 
); 

ho trovato questo approccio molto utile quando si scrivono applicazioni WPF MVVM. Immagina di avere uno scenario in cui vuoi invalidare un comando quando cambia una delle tre proprietà. Utilizzando il metodo normale si dovrebbe fare qualcosa di simile:

void PropertyChangedHandler(object sender, PropertyChangedEventArgs e) 
{ 
    switch (e.PropertyName) 
    { 
     case "Foo": 
     case "Bar": 
     case "Baz": 
      FooBarBazCommand.Invalidate(); 
      break; 
     ....    
    } 
} 

Se si modifica il nome di una qualsiasi delle proprietà ViewModel che sarà necessario ricordarsi di aggiornare il gestore di eventi per selezionare le proprietà corrette.

Uso della classe PropertyChangedHandler specificato sopra si può ottenere lo stesso risultato con il seguente:

aViewModel.OnPropertyChanged(
    handler: p => FooBarBazCommand.Invalidate(), 
    properties: aViewModel.GetProperties(
     p => p.Foo, 
     p => p.Bar, 
     p => p.Baz 
    ) 
); 

Questo ha ora a tempo di compilazione di sicurezza in modo Se una qualsiasi delle proprietà ViewModel vengono rinominati il ​​programma non riuscirà a compilare.

2

Con C# 6.0 è possibile utilizzare nameof. Puoi anche fare riferimento alla proprietà di una classe senza creare un'istanza di quella classe.

void model_PropertyChanged(object sender, PropertyChangedEventArgs e) 
{ 
    switch (e.PropertyName) 
    { 
     case nameof(ClassName.Property1): 
      ... 
     case nameof(ClassName.Property2): 
      ... 

     ....    
    } 
} 
Problemi correlati