Implementazione di INotifyPropertyChanged: esiste un modo migliore?


647

Microsoft avrebbe dovuto implementare qualcosa di scattante INotifyPropertyChanged, come nelle proprietà automatiche, basta specificare {get; set; notify;} che penso abbia molto senso farlo. O ci sono delle complicazioni per farlo?

Possiamo noi stessi implementare qualcosa come "notifica" nelle nostre proprietà. Esiste una soluzione elegante per l'implementazione INotifyPropertyChangednella tua classe o l'unico modo per farlo è aumentare l' PropertyChangedevento in ogni proprietà.

Altrimenti possiamo scrivere qualcosa per generare automaticamente il pezzo di codice per generare un PropertyChanged evento?




2
In alternativa, è possibile utilizzare DependencyObject e DependencyProperties. HA! Ho fatto un divertente.
Phil


5
Al momento non era possibile apportare modifiche a C # dato che avevamo un enorme registro di interdipendenze. Quindi, quando MVVM è nato, immagino, non abbiamo davvero fatto molti sforzi per risolvere questo problema e so che il team Patterns & Practices ha fatto alcuni passi avanti lungo la strada (quindi hai anche ottenuto MEF come parte di questo filo di ricerca). Oggi penso che [CallerMemberName] sia la risposta a quanto sopra.
Scott Barnes,

Risposte:


633

Senza usare qualcosa come PostSharp, la versione minima che uso usa qualcosa come:

public class Data : INotifyPropertyChanged
{
    // boiler-plate
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
    protected bool SetField<T>(ref T field, T value, string propertyName)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    // props
    private string name;
    public string Name
    {
        get { return name; }
        set { SetField(ref name, value, "Name"); }
    }
}

Ogni proprietà è quindi solo qualcosa del tipo:

    private string name;
    public string Name
    {
        get { return name; }
        set { SetField(ref name, value, "Name"); }
    }

che non è enorme; può anche essere usato come classe base, se lo si desidera. Il boolritorno da SetFieldti dice se era una no-op, nel caso in cui desideri applicare altra logica.


o ancora più facile con C # 5:

protected bool SetField<T>(ref T field, T value,
    [CallerMemberName] string propertyName = null)
{...}

che può essere chiamato così:

set { SetField(ref name, value); }

con cui il compilatore aggiungerà "Name"automaticamente il.


C # 6.0 semplifica l'implementazione:

protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

... e ora con C # 7:

protected void OnPropertyChanged(string propertyName)
   => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

protected bool SetField<T>(ref T field, T value,[CallerMemberName] string propertyName =  null)
{
    if (EqualityComparer<T>.Default.Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(propertyName);
    return true;
}

private string name;
public string Name
{
    get => name;
    set => SetField(ref name, value);
}

4
Bel trucco Marc! Ho suggerito un miglioramento per usare un'espressione lambda invece del nome della proprietà, vedi la mia risposta
Thomas Levesque,

7
@Thomas - la lambda va bene e va bene, ma aggiunge un sacco di spese generali per qualcosa che in realtà è molto semplice. Un trucco utile, ma non sono sicuro che sia sempre pratico.
Marc Gravell

14
@Marc - Sì, probabilmente può peggiorare le prestazioni ... Tuttavia mi piace molto il fatto che sia stato verificato in fase di compilazione ed è correttamente riformulato dal comando "Rinomina"
Thomas Levesque,

4
@Gusdor per fortuna, con C # 5 non c'è bisogno di scendere a compromessi - puoi ottenere il meglio da entrambi (come nota Pedro77)[CallerMemberName]
Marc Gravell

4
@Gusdor la lingua e il framework sono separati; puoi usare il compilatore C # 5, target .NET 4, e aggiungere tu stesso l'attributo mancante - funzionerà benissimo. Deve solo avere il nome corretto ed essere nello spazio dei nomi corretto. Non è necessario che si trovi in ​​un assembly specifico.
Marc Gravell

196

A partire da .Net 4.5 esiste finalmente un modo semplice per farlo.

.Net 4.5 introduce un nuovo attributo delle informazioni sul chiamante.

private void OnPropertyChanged<T>([CallerMemberName]string caller = null) {
     // make sure only to call this if the value actually changes

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

Probabilmente è una buona idea aggiungere anche un comparatore alla funzione.

EqualityComparer<T>.Default.Equals

Altri esempi qui e qui

Vedi anche Informazioni sul chiamante (C # e Visual Basic)


12
Brillante! Ma perché è generico?
abatishchev,

@abatishchev Immagino che non debba essere così, stavo solo giocando con l'idea di impostare anche la funzione. Vedrò se posso aggiornare la mia risposta fornire la soluzione completa. Gli esempi extra fanno un buon lavoro nel frattempo.
Daniel Little,

3
È stato introdotto da C # 5.0. Non ha nulla a che fare con .net 4.5, ma questa è un'ottima soluzione!
J. Lennon,

5
@J. Lennon .net 4.5 ha ancora qualcosa a che fare con esso, dopo che tutto l'attributo proviene da qualche parte msdn.microsoft.com/en-au/library/…
Daniel Little,

@Lavinski cambia la tua applicazione in es. .NET 3.5 e vedi cosa funzionerà (in vs2012)
J. Lennon,

162

Mi piace molto la soluzione di Marc, ma penso che possa essere leggermente migliorata per evitare di usare una "corda magica" (che non supporta il refactoring). Invece di usare il nome della proprietà come stringa, è facile renderlo un'espressione lambda:

private string name;
public string Name
{
    get { return name; }
    set { SetField(ref name, value, () => Name); }
}

Basta aggiungere i seguenti metodi al codice di Marc, farà il trucco:

protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
{
    if (selectorExpression == null)
        throw new ArgumentNullException("selectorExpression");
    MemberExpression body = selectorExpression.Body as MemberExpression;
    if (body == null)
        throw new ArgumentException("The body must be a member expression");
    OnPropertyChanged(body.Member.Name);
}

protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
{
    if (EqualityComparer<T>.Default.Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(selectorExpression);
    return true;
}

A proposito, questo è stato ispirato dall'URL aggiornato di questo post sul blog


6
C'è almeno un framework che utilizza questo metodo, ReactiveUI .
AlSki,

Molto tardi, questo significava passare attraverso la riflessione, il che significava un colpo di scena. Potrebbe essere accettabile, ma l'impostazione di una proprietà non è un luogo in cui mi piacerebbe che la mia applicazione passasse a molti cicli.
Bruno Brant,

1
@BrunoBrant Sei sicuro che ci sia un successo nelle prestazioni? Secondo il post del blog la riflessione avviene durante il tempo di compilazione piuttosto che in fase di esecuzione (ovvero riflessione statica).
Nathaniel Elkins,

6
Credo che tutto OnPropertyChanged <T> sia obsoleto con il nome dell'operatore di C # 6, rendendo questo mostro un po 'più elegante.
Traubenfuchs,

5
@Traubenfuchs, in realtà, l'attributo CallerMemberName di C # 5 lo rende ancora più semplice, dal momento che non è necessario passare nulla ...
Thomas Levesque,

120

C'è anche Fody che ha un componente aggiuntivo PropertyChanged , che ti consente di scrivere questo:

[ImplementPropertyChanged]
public class Person 
{        
    public string GivenNames { get; set; }
    public string FamilyName { get; set; }
}

... e al momento della compilazione inietta le notifiche modificate dalla proprietà.


7
Penso che questo sia esattamente ciò che OP stava cercando quando hanno chiesto "Possiamo noi stessi implementare qualcosa come" notifica "nelle nostre proprietà. Esiste una soluzione aggraziata per implementare INotifyPropertyChanged nella tua classe"
Ashoat

3
Questa è davvero l'unica soluzione aggraziata, e funziona perfettamente come ha detto @CADbloke. Ed ero anche scettico riguardo al tessitore, ma ho controllato / ricontrollato il codice IL dietro ed è perfetto, è semplice, fa tutto ciò di cui hai bisogno e nient'altro. Inoltre, aggancia e chiama qualsiasi nome di metodo che hai designato nella classe base per esso, indipendentemente dal fatto che NotifyOnProp ..., OnNotify ... non abbia importanza, quindi funziona bene con qualsiasi classe base che potresti avere e che implementa INotify .. .
NSGaga-per lo più inattiva

1
Puoi facilmente ricontrollare cosa sta facendo il tessitore, dare un'occhiata alla finestra di output della compilazione, elenca tutte le cose modificate da PropertyChanged. L'uso dell'estensione VScolorOutput con il motivo regex "Fody/.*?:",LogCustom2,Truelo evidenzia con il colore "Personalizzato 2". L'ho reso rosa brillante, quindi è facile da trovare. Solo Fody tutto, è il modo più semplice per fare qualsiasi cosa che abbia un sacco di digitazioni ripetitive.
Bloke CAD,

@mahmoudnezarsarhan no, non lo è, ricordo che c'è stato un leggero cambiamento nel modo in cui deve essere configurato, ma Fody PropertyChanged è ancora vivo e attivo.
Larry,

65

Penso che le persone dovrebbero prestare un po 'più di attenzione alla performance; influisce davvero sull'interfaccia utente quando ci sono molti oggetti da associare (pensa a una griglia con oltre 10.000 righe) o se il valore dell'oggetto cambia frequentemente (app di monitoraggio in tempo reale).

Ho preso varie implementazioni trovate qui e altrove e ho fatto un confronto; dai un'occhiata al confronto delle prestazioni delle implementazioni INotifyPropertyChanged .


Ecco una sbirciatina al risultato Implementazione vs Runtime


14
-1: non vi è sovraccarico di prestazioni: CallerMemberName viene modificato in valori letterali al momento della compilazione. Prova a decompilare la tua app.
JYL

ecco la domanda secondo e risposta: stackoverflow.com/questions/22580623/...
uli78

1
@JYL, hai ragione nel dire che CallerMemberName non ha aggiunto un grande overhead. Devo aver implementato qualcosa di sbagliato l'ultima volta che l'ho provato. Aggiornerò il blog e risponderò per riflettere in seguito il benchmark per l'implementazione di CallerMemberName e Fody.
Peijen,

1
Se hai una griglia di oltre 10.000 nell'interfaccia utente, probabilmente dovresti combinare approcci per gestire le prestazioni, come il paging in cui mostri solo 10, 50, 100, 250 hit per pagina ...
Austin Rhymer

Austin Rhymer, se disponi di dati di grandi dimensioni + 50 utilizza la virtualizzazione dei dati, non è necessario caricare tutti i dati, verranno caricati solo i dati visibili sull'area visualizzata per lo scolling corrente!
Bilal

38

Presento una classe Bindable nel mio blog all'indirizzo http://timoch.com/blog/2013/08/annoyed-with-inotifypropertychange/ Bindable utilizza un dizionario come borsa delle proprietà. È abbastanza facile aggiungere i sovraccarichi necessari affinché una sottoclasse possa gestire il proprio campo di supporto usando i parametri ref.

  • Nessuna corda magica
  • Nessun riflesso
  • Può essere migliorato per sopprimere la ricerca del dizionario predefinita

Il codice:

public class Bindable : INotifyPropertyChanged {
    private Dictionary<string, object> _properties = new Dictionary<string, object>();

    /// <summary>
    /// Gets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    protected T Get<T>([CallerMemberName] string name = null) {
        Debug.Assert(name != null, "name != null");
        object value = null;
        if (_properties.TryGetValue(name, out value))
            return value == null ? default(T) : (T)value;
        return default(T);
    }

    /// <summary>
    /// Sets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="name"></param>
    /// <remarks>Use this overload when implicitly naming the property</remarks>
    protected void Set<T>(T value, [CallerMemberName] string name = null) {
        Debug.Assert(name != null, "name != null");
        if (Equals(value, Get<T>(name)))
            return;
        _properties[name] = value;
        OnPropertyChanged(name);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Può essere usato in questo modo:

public class Contact : Bindable {
    public string FirstName {
        get { return Get<string>(); }
        set { Set(value); }
    }
}

2
Questa è una buona soluzione, ma l'unico aspetto negativo è che c'è un piccolo successo nelle prestazioni che coinvolge boxe / unboxing.
MCattle,

1
Suggerirei di usare protected T Get<T>(T defaultValue, [CallerMemberName] string name = null)e anche di controllare if (_properties.ContainsKey(name) && Equals(value, Get<T>(default(T), name)))Set (per aumentare e salvare quando impostato per la prima volta sul valore predefinito)
Miquel

1
@Miquel l'aggiunta del supporto per i valori predefiniti personalizzati può essere sicuramente utile, tuttavia si dovrebbe fare attenzione a sollevare l'evento modificato solo quando il valore è stato effettivamente modificato. L'impostazione di una proprietà sullo stesso valore che aveva non dovrebbe generare eventi. Devo ammettere che nella maggior parte dei casi è innocuo, tuttavia sono stato un po 'parecchie volte con proprietà impostate migliaia di volte sullo stesso valore con eventi che hanno distrutto la reattività dell'interfaccia utente.
TiMoch

1
@stakx Ho alcune applicazioni che si basano su questo per supportare il modello di ricordo per annulla / ripristina o per abilitare l'unità del modello di lavoro in applicazioni in cui nhibernate non è utilizzabile
TiMoch

1
Mi piace molto questa particolare soluzione: notazione breve, nessuna roba proxy dinamica, nessuna interferenza IL, ecc. Tuttavia, è possibile accorciarla rimuovendo la necessità di specificare T ogni volta per Get, rendendo Get return dinamico. Lo so, questo influisce sulle prestazioni di runtime, ma ora il codice per getter e setter può finalmente essere sempre lo stesso e in una riga , lodare il Signore! PS, dovresti prestare particolare attenzione al tuo metodo Get (una volta quando scrivi la classe base) quando restituisci i valori predefiniti per i valori come dinamici. Assicurati di restituire sempre i valori predefiniti corretti (è possibile farlo)
evilkos,

15

Non ho ancora avuto la possibilità di provare questo da solo, ma la prossima volta che sto impostando un progetto con un grande requisito per INotifyPropertyChanged ho intenzione di scrivere un attributo Postsharp che inietterà il codice al momento della compilazione. Qualcosa di simile a:

[NotifiesChange]
public string FirstName { get; set; }

Diventerà:

private string _firstName;

public string FirstName
{
   get { return _firstname; }
   set
   {
      if (_firstname != value)
      {
          _firstname = value;
          OnPropertyChanged("FirstName")
      }
   }
}

Non sono sicuro che funzionerà in pratica e ho bisogno di sedermi e provarlo, ma non vedo perché no. Potrebbe essere necessario che accetti alcuni parametri per le situazioni in cui è necessario attivare più di un OnPropertyChanged (se, ad esempio, avevo una proprietà FullName nella classe sopra)

Attualmente sto usando un modello personalizzato in Resharper, ma nonostante ciò mi stufo di tutte le mie proprietà che sono così lunghe.


Ah, una rapida ricerca su Google (cosa che avrei dovuto fare prima di scriverlo) mostra che almeno una persona ha fatto qualcosa di simile prima qui . Non esattamente quello che avevo in mente, ma abbastanza vicino da dimostrare che la teoria è buona.


6
Uno strumento gratuito chiamato Fody sembra fare la stessa cosa, funzionando come un generico iniettore di codice in fase di compilazione. È scaricabile in Nuget, così come i pacchetti di plugin PropertyChanged e PropertyChanging.
Triynko,

11

Sì, esiste sicuramente un modo migliore. Ecco qui:

Tutorial passo dopo passo ridotto da me, basato su questo utile articolo .

  • Crea un nuovo progetto
  • Installa il pacchetto core del castello nel progetto

Installa-Pacchetto Castle.Core

  • Installa solo le librerie di luci mvvm

Pacchetto di installazione MvvmLightLibs

  • Aggiungi due classi nel progetto:

NotifierInterceptor

public class NotifierInterceptor : IInterceptor
    {
        private PropertyChangedEventHandler handler;
        public static Dictionary<String, PropertyChangedEventArgs> _cache =
          new Dictionary<string, PropertyChangedEventArgs>();

        public void Intercept(IInvocation invocation)
        {
            switch (invocation.Method.Name)
            {
                case "add_PropertyChanged":
                    handler = (PropertyChangedEventHandler)
                              Delegate.Combine(handler, (Delegate)invocation.Arguments[0]);
                    invocation.ReturnValue = handler;
                    break;
                case "remove_PropertyChanged":
                    handler = (PropertyChangedEventHandler)
                              Delegate.Remove(handler, (Delegate)invocation.Arguments[0]);
                    invocation.ReturnValue = handler;
                    break;
                default:
                    if (invocation.Method.Name.StartsWith("set_"))
                    {
                        invocation.Proceed();
                        if (handler != null)
                        {
                            var arg = retrievePropertyChangedArg(invocation.Method.Name);
                            handler(invocation.Proxy, arg);
                        }
                    }
                    else invocation.Proceed();
                    break;
            }
        }

        private static PropertyChangedEventArgs retrievePropertyChangedArg(String methodName)
        {
            PropertyChangedEventArgs arg = null;
            _cache.TryGetValue(methodName, out arg);
            if (arg == null)
            {
                arg = new PropertyChangedEventArgs(methodName.Substring(4));
                _cache.Add(methodName, arg);
            }
            return arg;
        }
    }

ProxyCreator

public class ProxyCreator
{
    public static T MakeINotifyPropertyChanged<T>() where T : class, new()
    {
        var proxyGen = new ProxyGenerator();
        var proxy = proxyGen.CreateClassProxy(
          typeof(T),
          new[] { typeof(INotifyPropertyChanged) },
          ProxyGenerationOptions.Default,
          new NotifierInterceptor()
          );
        return proxy as T;
    }
}
  • Crea il tuo modello di visualizzazione, ad esempio:

-

 public class MainViewModel
    {
        public virtual string MainTextBox { get; set; }

        public RelayCommand TestActionCommand
        {
            get { return new RelayCommand(TestAction); }
        }

        public void TestAction()
        {
            Trace.WriteLine(MainTextBox);
        }
    }
  • Inserisci i binding in xaml:

    <TextBox Text="{Binding MainTextBox}" ></TextBox>
    <Button Command="{Binding TestActionCommand}" >Test</Button>
  • Inserisci la riga di codice nel file code-behind MainWindow.xaml.cs in questo modo:

DataContext = ProxyCreator.MakeINotifyPropertyChanged<MainViewModel>();

  • Godere.

inserisci qui la descrizione dell'immagine

Attenzione!!! Tutte le proprietà limitate devono essere decorate con la parola chiave virtual perché utilizzate dal proxy castle per l'override.


Sono interessato a sapere quale versione di Castle stai utilizzando. Sto usando 3.3.0 e il metodo CreateClassProxy non ha questi parametri: type, interfaces to apply, interceptors.
Estratto

Non importa, stavo usando il CreateClassProxy<T>metodo generico . Molto diverso ... hmmm, chiedendosi perché così limitato con il metodo generico. :(
Estratto di IA


5

Guarda qui : http://dotnet-forum.de/blogs/thearchitect/archive/2012/11/01/die-optimale-implementierung-des-inotifypropertychanged-interfaces.aspx

È scritto in tedesco, ma è possibile scaricare ViewModelBase.cs. Tutti i commenti nel file cs sono scritti in inglese.

Con questa classe ViewModelBase è possibile implementare proprietà associabili simili alle ben note proprietà di dipendenza:

public string SomeProperty
{
    get { return GetValue( () => SomeProperty ); }
    set { SetValue( () => SomeProperty, value ); }
}

1
Il collegamento è interrotto.
Guge,

4

Sulla base della risposta di Thomas che è stata adattata da una risposta di Marc, ho trasformato il codice modificato della proprietà riflettente in una classe base:

public abstract class PropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) 
            handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
    {
        if (selectorExpression == null)
            throw new ArgumentNullException("selectorExpression");
        var me = selectorExpression.Body as MemberExpression;

        // Nullable properties can be nested inside of a convert function
        if (me == null)
        {
            var ue = selectorExpression.Body as UnaryExpression;
            if (ue != null)
                me = ue.Operand as MemberExpression;
        }

        if (me == null)
            throw new ArgumentException("The body must be a member expression");

        OnPropertyChanged(me.Member.Name);
    }

    protected void SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression, params Expression<Func<object>>[] additonal)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return;
        field = value;
        OnPropertyChanged(selectorExpression);
        foreach (var item in additonal)
            OnPropertyChanged(item);
    }
}

L'utilizzo è lo stesso della risposta di Thomas, tranne per il fatto che è possibile passare ulteriori proprietà per la notifica. Ciò era necessario per gestire le colonne calcolate che devono essere aggiornate in una griglia.

private int _quantity;
private int _price;

public int Quantity 
{ 
    get { return _quantity; } 
    set { SetField(ref _quantity, value, () => Quantity, () => Total); } 
}
public int Price 
{ 
    get { return _price; } 
    set { SetField(ref _price, value, () => Price, () => Total); } 
}
public int Total { get { return _price * _quantity; } }

Ho questo alla guida di una raccolta di oggetti memorizzati in un BindingList esposti tramite un DataGridView. Mi ha eliminato la necessità di effettuare chiamate Refresh () manuali alla griglia.


4

Consentitemi di presentare il mio approccio chiamato Yappi . Appartiene ai generatori di classe derivati ​​dal proxy runtime, aggiungendo nuove funzionalità a un oggetto o tipo esistente, come il proxy dinamico di Caste Project.

Permette di implementare INotifyPropertyChanged una volta nella classe base e quindi di dichiarare le classi derivate nel seguente stile, supportando comunque INotifyPropertyChanged per le nuove proprietà:

public class Animal:Concept
{
    protected Animal(){}
    public virtual string Name { get; set; }
    public virtual int Age { get; set; }
}

La complessità della classe derivata o della costruzione proxy può essere nascosta dietro la seguente riga:

var animal = Concept.Create<Animal>.New();

E tutto il lavoro di implementazione di INotifyPropertyChanged può essere fatto in questo modo:

public class Concept:INotifyPropertyChanged
{
    //Hide constructor
    protected Concept(){}

    public static class Create<TConcept> where TConcept:Concept
    {
        //Construct derived Type calling PropertyProxy.ConstructType
        public static readonly Type Type = PropertyProxy.ConstructType<TConcept, Implementation<TConcept>>(new Type[0], true);
        //Create constructing delegate calling Constructor.Compile
        public static Func<TConcept> New = Constructor.Compile<Func<TConcept>>(Type);
    }


    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(PropertyChangedEventArgs eventArgs)
    {
        var caller = PropertyChanged;
        if(caller!=null)
        {
            caller(this, eventArgs);
        }
    }

    //define implementation
    public class Implementation<TConcept> : DefaultImplementation<TConcept> where TConcept:Concept
    {
        public override Func<TBaseType, TResult> OverrideGetter<TBaseType, TDeclaringType, TConstructedType, TResult>(PropertyInfo property)
        {
            return PropertyImplementation<TBaseType, TDeclaringType>.GetGetter<TResult>(property.Name);
        }
        /// <summary>
        /// Overriding property setter implementation.
        /// </summary>
        /// <typeparam name="TBaseType">Base type for implementation. TBaseType must be TConcept, and inherits all its constraints. Also TBaseType is TDeclaringType.</typeparam>
        /// <typeparam name="TDeclaringType">Type, declaring property.</typeparam>
        /// <typeparam name="TConstructedType">Constructed type. TConstructedType is TDeclaringType and TBaseType.</typeparam>
        /// <typeparam name="TResult">Type of property.</typeparam>
        /// <param name="property">PropertyInfo of property.</param>
        /// <returns>Delegate, corresponding to property setter implementation.</returns>
        public override Action<TBaseType, TResult> OverrideSetter<TBaseType, TDeclaringType, TConstructedType, TResult>(PropertyInfo property)
        {
            //This code called once for each declared property on derived type's initialization.
            //EventArgs instance is shared between all events for each concrete property.
            var eventArgs = new PropertyChangedEventArgs(property.Name);
            //get delegates for base calls.
            Action<TBaseType, TResult> setter = PropertyImplementation<TBaseType, TDeclaringType>.GetSetter<TResult>(property.Name);
            Func<TBaseType, TResult> getter = PropertyImplementation<TBaseType, TDeclaringType>.GetGetter<TResult>(property.Name);

            var comparer = EqualityComparer<TResult>.Default;

            return (pthis, value) =>
            {//This code executes each time property setter is called.
                if (comparer.Equals(value, getter(pthis))) return;
                //base. call
                setter(pthis, value);
                //Directly accessing Concept's protected method.
                pthis.OnPropertyChanged(eventArgs);
            };
        }
    }
}

È completamente sicuro per il refactoring, non utilizza riflessi dopo la costruzione del tipo e abbastanza veloce.


Perché è necessario il TDeclarationparametro type su PropertyImplementation? Sicuramente puoi trovare il tipo appropriato per chiamare (non callvirt) solo il getter / setter TImplementation?
Andrew Savinykh,

L'implementazione funziona nella maggior parte dei casi. Le eccezioni sono: 1. Proprietà ridefinite con "nuovo" keyvord C #. 2. Proprietà dell'implementazione esplicita dell'interfaccia.
Kelqualyn,

3

Tutte queste risposte sono molto carine.

La mia soluzione sta usando gli snippet di codice per fare il lavoro.

Questo utilizza la chiamata più semplice all'evento PropertyChanged.

Salva questo snippet e utilizzalo mentre usi lo snippet "fullprop".

la posizione è disponibile nel menu "Strumenti \ Gestore frammento di codice ..." in Visual Studio.

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
            <Title>inotifypropfull</Title>
            <Shortcut>inotifypropfull</Shortcut>
            <HelpUrl>http://ofirzeitoun.wordpress.com/</HelpUrl>
            <Description>Code snippet for property and backing field with notification</Description>
            <Author>Ofir Zeitoun</Author>
            <SnippetTypes>
                <SnippetType>Expansion</SnippetType>
            </SnippetTypes>
        </Header>
        <Snippet>
            <Declarations>
                <Literal>
                    <ID>type</ID>
                    <ToolTip>Property type</ToolTip>
                    <Default>int</Default>
                </Literal>
                <Literal>
                    <ID>property</ID>
                    <ToolTip>Property name</ToolTip>
                    <Default>MyProperty</Default>
                </Literal>
                <Literal>
                    <ID>field</ID>
                    <ToolTip>The variable backing this property</ToolTip>
                    <Default>myVar</Default>
                </Literal>
            </Declarations>
            <Code Language="csharp">
                <![CDATA[private $type$ $field$;

    public $type$ $property$
    {
        get { return $field$;}
        set { 
            $field$ = value;
            var temp = PropertyChanged;
            if (temp != null)
            {
                temp(this, new PropertyChangedEventArgs("$property$"));
            }
        }
    }
    $end$]]>
            </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>

Puoi modificare la chiamata come preferisci (per utilizzare le soluzioni sopra)


2

Se si utilizza la dinamica in .NET 4.5 non è necessario preoccuparsi INotifyPropertyChanged.

dynamic obj = new ExpandoObject();
obj.Name = "John";

se Nome è associato a qualche controllo, funziona bene.


1
degli svantaggi nell'utilizzare questo?
juFo

2

Un'altra soluzione combinata sta usando StackFrame:

public class BaseViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void Set<T>(ref T field, T value)
    {
        MethodBase method = new StackFrame(1).GetMethod();
        field = value;
        Raise(method.Name.Substring(4));
    }

    protected void Raise(string propertyName)
    {
        var temp = PropertyChanged;
        if (temp != null)
        {
            temp(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Uso:

public class TempVM : BaseViewModel
{
    private int _intP;
    public int IntP
    {
        get { return _intP; }
        set { Set<int>(ref _intP, value); }
    }
}

2
È veloce? L'accesso al frame dello stack non è vincolato ad alcuni requisiti di autorizzazione? È robusto in un contesto di utilizzo di async / await?
Stéphane Gourichon,

@ StéphaneGourichon No, non lo è. L'accesso al frame dello stack comporta un notevole aumento delle prestazioni nella maggior parte dei casi.
Bruno Brant,


Si noti che l'allineamento potrebbe nascondere il get_Foometodo in modalità Rilascio.
bytecode77

2

Ho creato un metodo di estensione nella mia libreria di base per il riutilizzo:

public static class INotifyPropertyChangedExtensions
{
    public static bool SetPropertyAndNotify<T>(this INotifyPropertyChanged sender,
               PropertyChangedEventHandler handler, ref T field, T value, 
               [CallerMemberName] string propertyName = "",
               EqualityComparer<T> equalityComparer = null)
    {
        bool rtn = false;
        var eqComp = equalityComparer ?? EqualityComparer<T>.Default;
        if (!eqComp.Equals(field,value))
        {
            field = value;
            rtn = true;
            if (handler != null)
            {
                var args = new PropertyChangedEventArgs(propertyName);
                handler(sender, args);
            }
        }
        return rtn;
    }
}

Funziona con .Net 4.5 a causa di CallerMemberNameAttribute . Se si desidera utilizzarlo con una versione precedente .Net, è necessario modificare la dichiarazione del metodo da: ...,[CallerMemberName] string propertyName = "", ...a...,string propertyName, ...

Uso:

public class Dog : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    string _name;

    public string Name
    {
        get { return _name; }
        set
        {
            this.SetPropertyAndNotify(PropertyChanged, ref _name, value);
        }
    }
}

2

Ho risolto in questo modo (è un po 'laborioso, ma è sicuramente il più veloce in fase di esecuzione).

In VB (scusate, ma penso che non sia difficile tradurlo in C #), faccio questa sostituzione con RE:

(?<Attr><(.*ComponentModel\.)Bindable\(True\)>)( |\r\n)*(?<Def>(Public|Private|Friend|Protected) .*Property )(?<Name>[^ ]*) As (?<Type>.*?)[ |\r\n](?![ |\r\n]*Get)

con:

Private _${Name} As ${Type}\r\n${Attr}\r\n${Def}${Name} As ${Type}\r\nGet\r\nReturn _${Name}\r\nEnd Get\r\nSet (Value As ${Type})\r\nIf _${Name} <> Value Then \r\n_${Name} = Value\r\nRaiseEvent PropertyChanged(Me, New ComponentModel.PropertyChangedEventArgs("${Name}"))\r\nEnd If\r\nEnd Set\r\nEnd Property\r\n

Questo transofrm tutto il codice in questo modo:

<Bindable(True)>
Protected Friend Property StartDate As DateTime?

In

Private _StartDate As DateTime?
<Bindable(True)>
Protected Friend Property StartDate As DateTime?
    Get
        Return _StartDate
    End Get
    Set(Value As DateTime?)
        If _StartDate <> Value Then
            _StartDate = Value
            RaiseEvent PropertyChange(Me, New ComponentModel.PropertyChangedEventArgs("StartDate"))
        End If
    End Set
End Property

E se voglio avere un codice più leggibile, posso essere il contrario solo facendo la seguente sostituzione:

Private _(?<Name>.*) As (?<Type>.*)[\r\n ]*(?<Attr><(.*ComponentModel\.)Bindable\(True\)>)[\r\n ]*(?<Def>(Public|Private|Friend|Protected) .*Property )\k<Name> As \k<Type>[\r\n ]*Get[\r\n ]*Return _\k<Name>[\r\n ]*End Get[\r\n ]*Set\(Value As \k<Type>\)[\r\n ]*If _\k<Name> <> Value Then[\r\n ]*_\k<Name> = Value[\r\n ]*RaiseEvent PropertyChanged\(Me, New (.*ComponentModel\.)PropertyChangedEventArgs\("\k<Name>"\)\)[\r\n ]*End If[\r\n ]*End Set[\r\n ]*End Property

Con

${Attr} ${Def} ${Name} As ${Type}

Lancio per sostituire il codice IL del metodo set, ma non riesco a scrivere molto codice compilato in IL ... Se un giorno lo scrivo, ti dico!


2

Lo tengo in giro come frammento. C # 6 aggiunge una bella sintassi per invocare il gestore.

// INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

private void Set<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
{
    if (EqualityComparer<T>.Default.Equals(property, value) == false)
    {
        property = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

2

Ecco una versione Unity3D o non CallerMemberName di NotifyPropertyChanged

public abstract class Bindable : MonoBehaviour, INotifyPropertyChanged
{
    private readonly Dictionary<string, object> _properties = new Dictionary<string, object>();
    private static readonly StackTrace stackTrace = new StackTrace();
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    ///     Resolves a Property's name from a Lambda Expression passed in.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="property"></param>
    /// <returns></returns>
    internal string GetPropertyName<T>(Expression<Func<T>> property)
    {
        var expression = (MemberExpression) property.Body;
        var propertyName = expression.Member.Name;

        Debug.AssertFormat(propertyName != null, "Bindable Property shouldn't be null!");
        return propertyName;
    }

    #region Notification Handlers

    /// <summary>
    ///     Notify's all other objects listening that a value has changed for nominated propertyName
    /// </summary>
    /// <param name="propertyName"></param>
    internal void NotifyOfPropertyChange(string propertyName)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    /// <summary>
    ///     Notifies subscribers of the property change.
    /// </summary>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="property">The property expression.</param>
    internal void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property)
    {
        var propertyName = GetPropertyName(property);
        NotifyOfPropertyChange(propertyName);
    }

    /// <summary>
    ///     Raises the <see cref="PropertyChanged" /> event directly.
    /// </summary>
    /// <param name="e">The <see cref="PropertyChangedEventArgs" /> instance containing the event data.</param>
    internal void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    #endregion

    #region Getters

    /// <summary>
    ///     Gets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    internal T Get<T>(Expression<Func<T>> property)
    {
        var propertyName = GetPropertyName(property);
        return Get<T>(GetPropertyName(property));
    }

    /// <summary>
    ///     Gets the value of a property automatically based on its caller.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    internal T Get<T>()
    {
        var name = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name;
        return Get<T>(name);
    }

    /// <summary>
    ///     Gets the name of a property based on a string.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    internal T Get<T>(string name)
    {
        object value = null;
        if (_properties.TryGetValue(name, out value))
            return value == null ? default(T) : (T) value;
        return default(T);
    }

    #endregion

    #region Setters

    /// <summary>
    ///     Sets the value of a property whilst automatically looking up its caller name.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    internal void Set<T>(T value)
    {
        var propertyName = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name;
        Set(value, propertyName);
    }

    /// <summary>
    ///     Sets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="name"></param>
    internal void Set<T>(T value, string propertyName)
    {
        Debug.Assert(propertyName != null, "name != null");
        if (Equals(value, Get<T>(propertyName)))
            return;
        _properties[propertyName] = value;
        NotifyOfPropertyChange(propertyName);
    }

    /// <summary>
    ///     Sets the value of a property based off an Expression (()=>FieldName)
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="property"></param>
    internal void Set<T>(T value, Expression<Func<T>> property)
    {
        var propertyName = GetPropertyName(property);

        Debug.Assert(propertyName != null, "name != null");

        if (Equals(value, Get<T>(propertyName)))
            return;
        _properties[propertyName] = value;
        NotifyOfPropertyChange(propertyName);
    }

    #endregion
}

Questo codice consente di scrivere campi di supporto proprietà come questo:

  public string Text
    {
        get { return Get<string>(); }
        set { Set(value); }
    }

Inoltre, nel resharper se crei uno snippet di pattern / ricerca puoi anche automatizzare il tuo flusso di lavoro convertendo semplici campi di prop nel supporto sopra.

Modello di ricerca:

public $type$ $fname$ { get; set; }

Sostituisci modello:

public $type$ $fname$
{
    get { return Get<$type$>(); }
    set { Set(value); }
}

2

Ho scritto un articolo che aiuta in questo ( https://msdn.microsoft.com/magazine/mt736453 ). È possibile utilizzare il pacchetto NuGet SolSoft.DataBinding. Quindi puoi scrivere codice in questo modo:

public class TestViewModel : IRaisePropertyChanged
{
  public TestViewModel()
  {
    this.m_nameProperty = new NotifyProperty<string>(this, nameof(Name), null);
  }

  private readonly NotifyProperty<string> m_nameProperty;
  public string Name
  {
    get
    {
      return m_nameProperty.Value;
    }
    set
    {
      m_nameProperty.SetValue(value);
    }
  }

  // Plus implement IRaisePropertyChanged (or extend BaseViewModel)
}

Benefici:

  1. la classe di base è facoltativa
  2. nessuna riflessione su ogni "valore impostato"
  3. possono avere proprietà che dipendono da altre proprietà e generano automaticamente tutti gli eventi appropriati (l'articolo ne ha un esempio)

2

Mentre ci sono ovviamente molti modi per farlo, ad eccezione delle risposte magiche di AOP, nessuna delle risposte sembra guardare all'impostazione della proprietà di un Modello direttamente dal modello di vista senza avere un campo locale come riferimento.

Il problema è che non puoi fare riferimento a una proprietà. Tuttavia, è possibile utilizzare un'azione per impostare quella proprietà.

protected bool TrySetProperty<T>(Action<T> property, T newValue, T oldValue, [CallerMemberName] string propertyName = null)
{
    if (EqualityComparer<T>.Default.Equals(oldValue, newValue))
    {
        return false;
    }

    property(newValue);
    RaisePropertyChanged(propertyName);
    return true;
}

Questo può essere usato come il seguente estratto di codice.

public int Prop {
    get => model.Prop;
    set => TrySetProperty(x => model.Prop = x, value, model.Prop);
}

Dai un'occhiata a questo repository BitBucket per un'implementazione completa del metodo e alcuni modi diversi per ottenere lo stesso risultato, incluso un metodo che utilizza LINQ e un metodo che utilizza la riflessione. Si noti che questi metodi hanno prestazioni più lente.


1

Altre cose che potresti prendere in considerazione quando implementi questo tipo di proprietà è il fatto che INotifyPropertyChang * ed * ing utilizzano entrambe le classi di argomenti di eventi.

Se si dispone di un numero elevato di proprietà che vengono impostate, il numero di istanze della classe dell'argomento evento può essere enorme, è necessario considerare di memorizzarle nella cache in quanto sono una delle aree in cui può verificarsi un'esplosione di stringhe.

Dai un'occhiata a questa implementazione e spiegazione del perché è stata concepita.

Blog di Josh Smiths


1

Ho appena trovato ActiveSharp - Automatic INotifyPropertyChanged , devo ancora usarlo, ma sembra buono.

Per citare dal suo sito web ...


Invia notifiche di modifica della proprietà senza specificare il nome della proprietà come stringa.

Invece, scrivi proprietà come questa:

public int Foo
{
    get { return _foo; }
    set { SetValue(ref _foo, value); }  // <-- no property name here
}

Si noti che non è necessario includere il nome della proprietà come stringa. ActiveSharp lo risolve in modo affidabile e corretto. Funziona in base al fatto che l'implementazione della proprietà passa il campo di supporto (_foo) per rif. (ActiveSharp utilizza quella chiamata "per ref" per identificare quale campo di supporto è stato passato e dal campo identifica la proprietà).


1

Un'idea usando la riflessione:

class ViewModelBase : INotifyPropertyChanged {

    public event PropertyChangedEventHandler PropertyChanged;

    bool Notify<T>(MethodBase mb, ref T oldValue, T newValue) {

        // Get Name of Property
        string name = mb.Name.Substring(4);

        // Detect Change
        bool changed = EqualityComparer<T>.Default.Equals(oldValue, newValue);

        // Return if no change
        if (!changed) return false;

        // Update value
        oldValue = newValue;

        // Raise Event
        if (PropertyChanged != null) {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }//if

        // Notify caller of change
        return true;

    }//method

    string name;

    public string Name {
        get { return name; }
        set {
            Notify(MethodInfo.GetCurrentMethod(), ref this.name, value);
        }
    }//method

}//class

Questo è piuttosto interessante, mi piace più dell'approccio espressivo. Sul lato negativo, dovrebbe essere più lento.
nawfal,

1

Mi rendo conto che questa domanda ha già una risposta gazillion, ma nessuno di loro si è sentito abbastanza giusto per me. Il mio problema è che non voglio hit di performance e sono disposto a sopportare un po 'di verbosità solo per questo motivo. Inoltre, non mi importa troppo delle proprietà automatiche, il che mi ha portato alla seguente soluzione:

public abstract class AbstractObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual bool SetValue<TKind>(ref TKind Source, TKind NewValue, params string[] Notify)
    {
        //Set value if the new value is different from the old
        if (!Source.Equals(NewValue))
        {
            Source = NewValue;

            //Notify all applicable properties
            foreach (var i in Notify)
                OnPropertyChanged(i);

            return true;
        }

        return false;
    }

    public AbstractObject()
    {
    }
}

In altre parole, la soluzione sopra è utile se non ti dispiace farlo:

public class SomeObject : AbstractObject
{
    public string AnotherProperty
    {
        get
        {
            return someProperty ? "Car" : "Plane";
        }
    }

    bool someProperty = false;
    public bool SomeProperty
    {
        get
        {
            return someProperty;
        }
        set
        {
            SetValue(ref someProperty, value, "SomeProperty", "AnotherProperty");
        }
    }

    public SomeObject() : base()
    {
    }
}

Professionisti

  • Nessun riflesso
  • Notifica solo se vecchio valore! = Nuovo valore
  • Notifica più proprietà contemporaneamente

Contro

  • Nessuna proprietà automatica (puoi aggiungere supporto per entrambi, però!)
  • Un po 'di verbosità
  • Pugilato (piccolo successo?)

Ahimè, è ancora meglio che farlo,

set
{
    if (!someProperty.Equals(value))
    {
        someProperty = value;
        OnPropertyChanged("SomeProperty");
        OnPropertyChanged("AnotherProperty");
    }
}

Per ogni singola proprietà, che diventa un incubo con la verbosità aggiuntiva ;-(

Nota, non pretendo che questa soluzione sia migliore dal punto di vista delle prestazioni rispetto alle altre, solo che è una soluzione praticabile per coloro a cui non piacciono le altre soluzioni presentate.


1

Ho creato questa classe base per implementare il modello osservabile, praticamente fa quello che ti serve ( "automaticamente" implementando il set e ottieni). Ho trascorso un'ora su questo come prototipo, quindi non ha molti test unitari, ma dimostra il concetto. Nota che usa il Dictionary<string, ObservablePropertyContext>per rimuovere la necessità di campi privati.

  public class ObservableByTracking<T> : IObservable<T>
  {
    private readonly Dictionary<string, ObservablePropertyContext> _expando;
    private bool _isDirty;

    public ObservableByTracking()
    {
      _expando = new Dictionary<string, ObservablePropertyContext>();

      var properties = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).ToList();
      foreach (var property in properties)
      {
        var valueContext = new ObservablePropertyContext(property.Name, property.PropertyType)
        {
          Value = GetDefault(property.PropertyType)
        };

        _expando[BuildKey(valueContext)] = valueContext;
      }
    }

    protected void SetValue<T>(Expression<Func<T>> expression, T value)
    {
      var keyContext = GetKeyContext(expression);
      var key = BuildKey(keyContext.PropertyName, keyContext.PropertyType);

      if (!_expando.ContainsKey(key))
      {
        throw new Exception($"Object doesn't contain {keyContext.PropertyName} property.");
      }

      var originalValue = (T)_expando[key].Value;
      if (EqualityComparer<T>.Default.Equals(originalValue, value))
      {
        return;
      }

      _expando[key].Value = value;
      _isDirty = true;
    }

    protected T GetValue<T>(Expression<Func<T>> expression)
    {
      var keyContext = GetKeyContext(expression);
      var key = BuildKey(keyContext.PropertyName, keyContext.PropertyType);

      if (!_expando.ContainsKey(key))
      {
        throw new Exception($"Object doesn't contain {keyContext.PropertyName} property.");
      }

      var value = _expando[key].Value;
      return (T)value;
    }

    private KeyContext GetKeyContext<T>(Expression<Func<T>> expression)
    {
      var castedExpression = expression.Body as MemberExpression;
      if (castedExpression == null)
      {
        throw new Exception($"Invalid expression.");
      }

      var parameterName = castedExpression.Member.Name;

      var propertyInfo = castedExpression.Member as PropertyInfo;
      if (propertyInfo == null)
      {
        throw new Exception($"Invalid expression.");
      }

      return new KeyContext {PropertyType = propertyInfo.PropertyType, PropertyName = parameterName};
    }

    private static string BuildKey(ObservablePropertyContext observablePropertyContext)
    {
      return $"{observablePropertyContext.Type.Name}.{observablePropertyContext.Name}";
    }

    private static string BuildKey(string parameterName, Type type)
    {
      return $"{type.Name}.{parameterName}";
    }

    private static object GetDefault(Type type)
    {
      if (type.IsValueType)
      {
        return Activator.CreateInstance(type);
      }
      return null;
    }

    public bool IsDirty()
    {
      return _isDirty;
    }

    public void SetPristine()
    {
      _isDirty = false;
    }

    private class KeyContext
    {
      public string PropertyName { get; set; }
      public Type PropertyType { get; set; }
    }
  }

  public interface IObservable<T>
  {
    bool IsDirty();
    void SetPristine();
  }

Ecco l'uso

public class ObservableByTrackingTestClass : ObservableByTracking<ObservableByTrackingTestClass>
  {
    public ObservableByTrackingTestClass()
    {
      StringList = new List<string>();
      StringIList = new List<string>();
      NestedCollection = new List<ObservableByTrackingTestClass>();
    }

    public IEnumerable<string> StringList
    {
      get { return GetValue(() => StringList); }
      set { SetValue(() => StringIList, value); }
    }

    public IList<string> StringIList
    {
      get { return GetValue(() => StringIList); }
      set { SetValue(() => StringIList, value); }
    }

    public int IntProperty
    {
      get { return GetValue(() => IntProperty); }
      set { SetValue(() => IntProperty, value); }
    }

    public ObservableByTrackingTestClass NestedChild
    {
      get { return GetValue(() => NestedChild); }
      set { SetValue(() => NestedChild, value); }
    }

    public IList<ObservableByTrackingTestClass> NestedCollection
    {
      get { return GetValue(() => NestedCollection); }
      set { SetValue(() => NestedCollection, value); }
    }

    public string StringProperty
    {
      get { return GetValue(() => StringProperty); }
      set { SetValue(() => StringProperty, value); }
    }
  }

1

Suggerisco di usare ReactiveProperty. Questo è il metodo più breve tranne Fody.

public class Data : INotifyPropertyChanged
{
    // boiler-plate
    ...
    // props
    private string name;
    public string Name
    {
        get { return name; }
        set { SetField(ref name, value, "Name"); }
    }
}

anziché

public class Data
{
    // Don't need boiler-plate and INotifyPropertyChanged

    // props
    public ReactiveProperty<string> Name { get; } = new ReactiveProperty<string>();
}

( DOCS )


0

Un'altra idea ...

 public class ViewModelBase : INotifyPropertyChanged
{
    private Dictionary<string, object> _propertyStore = new Dictionary<string, object>();
    protected virtual void SetValue<T>(T value, [CallerMemberName] string propertyName="") {
        _propertyStore[propertyName] = value;
        OnPropertyChanged(propertyName);
    }
    protected virtual T GetValue<T>([CallerMemberName] string propertyName = "")
    {
        object ret;
        if (_propertyStore.TryGetValue(propertyName, out ret))
        {
            return (T)ret;
        }
        else
        {
            return default(T);
        }
    }

    //Usage
    //public string SomeProperty {
    //    get { return GetValue<string>();  }
    //    set { SetValue(value); }
    //}

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        var temp = PropertyChanged;
        if (temp != null)
            temp.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

0

=> qui la mia soluzione con le seguenti funzionalità

 public ResourceStatus Status
 {
     get { return _status; }
     set
     {
         _status = value;
         Notify(Npcea.Status,Npcea.Comments);
     }
 }
  1. nessuna refelction
  2. breve notazione
  3. nessuna stringa magica nel tuo codice aziendale
  4. Riutilizzabilità di PropertyChangedEventArgs nell'applicazione
  5. Possibilità di notificare più proprietà in un'unica istruzione

0

Usa questo

using System;
using System.ComponentModel;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;


public static class ObservableFactory
{
    public static T Create<T>(T target)
    {
        if (!typeof(T).IsInterface)
            throw new ArgumentException("Target should be an interface", "target");

        var proxy = new Observable<T>(target);
        return (T)proxy.GetTransparentProxy();
    }
}

internal class Observable<T> : RealProxy, INotifyPropertyChanged, INotifyPropertyChanging
{
    private readonly T target;

    internal Observable(T target)
        : base(ImplementINotify(typeof(T)))
    {
        this.target = target;
    }

    public override IMessage Invoke(IMessage msg)
    {
        var methodCall = msg as IMethodCallMessage;

        if (methodCall != null)
        {
            return HandleMethodCall(methodCall);
        }

        return null;
    }

    public event PropertyChangingEventHandler PropertyChanging;
    public event PropertyChangedEventHandler PropertyChanged;



    IMessage HandleMethodCall(IMethodCallMessage methodCall)
    {
        var isPropertySetterCall = methodCall.MethodName.StartsWith("set_");
        var propertyName = isPropertySetterCall ? methodCall.MethodName.Substring(4) : null;

        if (isPropertySetterCall)
        {
            OnPropertyChanging(propertyName);
        }

        try
        {
            object methodCalltarget = target;

            if (methodCall.MethodName == "add_PropertyChanged" || methodCall.MethodName == "remove_PropertyChanged"||
                methodCall.MethodName == "add_PropertyChanging" || methodCall.MethodName == "remove_PropertyChanging")
            {
                methodCalltarget = this;
            }

            var result = methodCall.MethodBase.Invoke(methodCalltarget, methodCall.InArgs);

            if (isPropertySetterCall)
            {
                OnPropertyChanged(methodCall.MethodName.Substring(4));
            }

            return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
        }
        catch (TargetInvocationException invocationException)
        {
            var exception = invocationException.InnerException;
            return new ReturnMessage(exception, methodCall);
        }
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanging(string propertyName)
    {
        var handler = PropertyChanging;
        if (handler != null) handler(this, new PropertyChangingEventArgs(propertyName));
    }

    public static Type ImplementINotify(Type objectType)
    {
        var tempAssemblyName = new AssemblyName(Guid.NewGuid().ToString());

        var dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
            tempAssemblyName, AssemblyBuilderAccess.RunAndCollect);

        var moduleBuilder = dynamicAssembly.DefineDynamicModule(
            tempAssemblyName.Name,
            tempAssemblyName + ".dll");

        var typeBuilder = moduleBuilder.DefineType(
            objectType.FullName, TypeAttributes.Public | TypeAttributes.Interface | TypeAttributes.Abstract);

        typeBuilder.AddInterfaceImplementation(objectType);
        typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanged));
        typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanging));
        var newType = typeBuilder.CreateType();
        return newType;
    }
}

}

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.