Applicazione della corretta implementazione di INotifyPropertyChanged con CodeContracts - "richiede non dimostrato"

StackOverflow https://stackoverflow.com/questions/1621921

Domanda

Sto cercando un modo semplice per imporre la corretta implementazione di INotifyPropertyChanged, cioè quando viene sollevato PropertyChanged, deve fare riferimento a una proprietà che è effettivamente definita. Ho provato a farlo con i nuovi strumenti CodeContract di Microsoft, ma continuo a ricevere l'avviso "CodeContracts: richiede non dimostrato". Ecco il mio codice ...

public sealed class MyClass : INotifyPropertyChanged
{
    private int myProperty;
    public int MyProperty
    {
        get
        {
            return myProperty;
        }
        set
        {
            if (myProperty == value)
            {
                return;
            }

            myProperty = value;
            OnPropertyChanged("MyProperty");
        }
    }

    private void OnPropertyChanged(string propertyName)
    {
        Contract.Requires(GetType().GetProperties().Any(x => x.Name == propertyName));

        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Esiste un modo per far funzionare tutto questo?

È stato utile?

Soluzione

Suppongo che intendi con gli strumenti di analisi statica? (Mi aspetto che il controllo di runtime funzioni, almeno - e si potrebbe presumibilmente lasciarlo nelle build di debug). Dubito che questo sia qualcosa che l'analisi statica sarà in grado di vedere - GetType (). GetProperties () è semplicemente troppo complesso, ecc.

In breve; Ne dubito ... Lambdas ( Expression ) è un'opzione, ma sono molto più lenti del passaggio di una stringa.

Altri suggerimenti

Ok, prima di tutto, a questo scopo utilizzo personalmente l'implementazione di ObservableObject dalla fondazione MVVM . È un controllo di runtime solo build DEBUG quasi identico al tuo.

public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged(string propertyName)
{
    this.VerifyPropertyName(propertyName);

    PropertyChangedEventHandler handler = this.PropertyChanged;
    if (handler != null)
    {
        var e = new PropertyChangedEventArgs(propertyName);
        handler(this, e);
    }
}

[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
    // Verify that the property name matches a real,  
    // public, instance property on this object.
    if (TypeDescriptor.GetProperties(this)[propertyName] == null)
    {
        string msg = "Invalid property name: " + propertyName;

        if (this.ThrowOnInvalidPropertyName)
            throw new Exception(msg);
        else
            Debug.Fail(msg);
    }
}

Probabilmente è il modo più semplice, ma presenta alcuni svantaggi: devi essere in grado di ereditare da qualche classe di base, funziona solo in fase di esecuzione (anche se questo è sempre stato abbastanza nella mia esperienza di wpf), sembra sicuramente un " cerotto " per un controllo statico mancante.

Esistono diversi modi per abilitare l'analisi statica / strumenti statici per questo caso:

  1. Come dice Marc, usa la notazione lambda ed estrae stringa in fase di esecuzione .
  2. Scrivi una regola FxCop personalizzata .
  3. Usa uno strumento AOP per post-processare il codice con qualche meta-markup.

Per quanto riguarda CodeContracts, credo che non sia ancora abbastanza maturo per gestire questo tipo di controlli nell'analisi statica. Immagina, deve analizzare il tuo lambda, capire come può essere fallito da un propertyName sbagliato, trovare tutte le chiamate a questo metodo, capire tutti i possibili input, ecc. È solo uno strumento sbagliato per quello tipo di controllo.

Il modo in cui l'ho fatto in passato è usare la nostra buona amica Lambda. Utilizzando Expressions possiamo passare le proprietà stesse all'implementazione di OnPropertyChanges e utilizzare l'albero delle espressioni per estrarre la proprietà. Questo ti dà il controllo del tempo di compilazione dei membri per cui stai generando l'evento PropertyChanged.

Naturalmente l'uso di Expression dipenderà interamente dal tipo di performance di cui hai bisogno.

Vedi lo snippet di codice qui sotto:

using System;
using System.Linq;
using System.ComponentModel;
using System.Linq.Expressions;

namespace OnNotifyUsingLambda
{
    public class MainClass : INotifyPropertyChanged
    {
         public static void Main (string[] args) { new MainClass().Run();}
         public void Run()
         {
              this.PropertyChanged += (sender, e) => Console.WriteLine(e.PropertyName);
              MyProperty = "Hello";
         }

         private string myProperty;
         public string MyProperty  
         {
             get
             {
                 return myProperty;
             }
             set
             {
                 myProperty = value;
                 // call our OnPropertyChanged with our lamba expression, passing ourselves.
                 // voila compile time checking that we haven't messed up!
                 OnPropertyChanged(x => x.MyProperty); 
              }
         }  

         /// <summary>
         /// Fires the PropertyChanged for a property on our class.
         /// </summary>
         /// <param name="property">
         /// A <see cref="Expression<Func<MainClass, System.Object>>"/> that contains the 
         /// property we want to raise the event for.
         /// </param>
         private void OnPropertyChanged (Expression<Func<MainClass, object>> property)
         {
             // pull out the member expression (ie mainClass.MyProperty)
             var expr = (MemberExpression)property.Body; 

             if (PropertyChanged != null)
             {
                 // Extract everything after the period, which is our property name.
                 var propName = expr.ToString ().Split (new[] { '.' })[1];
                 PropertyChanged (this, new PropertyChangedEventArgs(propName));
             }
          }

          public event PropertyChangedEventHandler PropertyChanged;
     }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top