INotifyPropertyChanged uygulamak - daha iyi bir yol var mı?


647

Microsoft INotifyPropertyChanged, otomatik özelliklerde olduğu gibi hızlı bir şey uygulamış olmalıydı , sadece {get; set; notify;} bunu yapmanın çok mantıklı olduğunu düşünüyorum. Yoksa bunu yapmak için herhangi bir komplikasyon var mı?

Mülklerimize 'bildir' gibi bir şey uygulayabilir miyiz. INotifyPropertyChangedSınıfınızda uygulamak için zarif bir çözüm var mı ya da bunu yapmanın tek yolu PropertyChanged, her mülkteki olayı yükseltmektir .

Değilse, PropertyChanged olayı yükseltmek için kod parçasını otomatik olarak oluşturmak için bir şeyler yazabilir miyiz ?



7
yukarıdaki bağlantı öldü. github.com/SimonCropp/NotifyPropertyWeaver
prime23

2
Bunun yerine DependencyObject ve DependencyProperties kullanabilirsiniz. HA! Komik bir şey yaptım.
Phil


5
C # 'da değişiklik yapmak mümkün değildi, çünkü bağımlılıkların büyük bir arka günlüğü vardı. MVVM doğduğunda sanırım, bu sorunu çözmek için çok fazla çaba sarf etmedik ve Patterns & Practices ekibinin yol boyunca birkaç kez gittiğini biliyorum (bu yüzden bunun bir parçası olarak MEF var araştırma dizisi). Bugün [CallerMemberName] yukarıda yanıt olduğunu düşünüyorum.
Scott Barnes

Yanıtlar:


633

Postharp gibi bir şey kullanmadan, kullandığım minimal sürüm aşağıdaki gibi bir şey kullanır:

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

Her mülk o zaman şöyle olur:

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

ki bu çok büyük değil; isterseniz temel sınıf olarak da kullanılabilir. boolDönüş SetFielddiğer mantığı uygulamak istediğiniz durumda o, no-op olsaydı söyler.


veya C # 5 ile daha da kolay:

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

bu şekilde adlandırılabilir:

set { SetField(ref name, value); }

derleyicinin "Name"otomatik olarak ekleneceği .


C # 6.0 uygulamayı kolaylaştırır:

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

... ve şimdi C # 7 ile:

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
Güzel numara Marc! Mülk adı yerine lambda ifadesi kullanmak için bir iyileştirme önerdim, cevabımı görün
Thomas Levesque

7
@Thomas - lambda iyi ve iyi, ama aslında çok basit bir şey için çok fazla ek yük ekler. Kullanışlı bir hile, ama her zaman pratik olduğundan emin değilim.
Marc Gravell

14
@Marc - Evet, muhtemelen performansı düşürebilir ... Ancak derleme zamanında kontrol edildiğini ve "Yeniden adlandır" komutu ile doğru bir şekilde yeniden düzenlendiğini seviyorum
Thomas Levesque

4
@Gusdor Neyse ki, C # 5 ile uzlaşmaya gerek yok - her ikisinden de en iyisini elde edebilirsiniz (Pedro77 notları gibi)[CallerMemberName]
Marc Gravell

4
@Guzdor dil ve çerçeve ayrıdır; C # 5 derleyicisini kullanabilir, .NET 4'ü hedefleyebilir ve eksik özniteliği kendiniz ekleyebilirsiniz - iyi çalışır. Sadece doğru isme sahip olmalı ve doğru isim alanında olmalı. Belirli bir montajda olması gerekmez.
Marc Gravell

196

Net 4.5'ten itibaren bunu yapmanın kolay bir yolu var.

.Net 4.5 yeni bir Arayan Bilgi Öznitelikleri sunar.

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

İşleve de bir karşılaştırıcı eklemek iyi bir fikir olabilir.

EqualityComparer<T>.Default.Equals

Burada ve burada daha fazla örnek

Ayrıca bkz. Arayan Bilgisi (C # ve Visual Basic)


12
Parlak! Ama neden jenerik?
abatishchev

@abatishchev Sanırım olması gerekmiyor, sadece fonksiyonun özelliği ayarlaması fikriyle oynuyordum. Tam çözümü sağlamak için cevabımı güncelleyip güncelleyemeyeceğimi göreceğim. Ekstra örnekler bu arada iyi bir iş çıkarır.
Daniel Little

3
C # 5.0 ile tanıtıldı. .Net 4.5 ile ilgisi yok, ama bu harika bir çözüm!
J. Lennon

5
@J. Lennon .net 4.5 hala onunla ilgili bir şey var, tüm özellik bir yerden geliyor msdn.microsoft.com/en-au/library/…
Daniel Little

@Lavinski, uygulamanızı örneğin .NET 3.5 olarak değiştirin ve neyin işe yarayacağını görün (vs2012'de)
J. Lennon

162

Marc'ın çözümünü gerçekten çok seviyorum, ama bir "sihirli dize" (yeniden düzenleme desteklemiyor) kullanmaktan kaçınmak için biraz geliştirilebileceğini düşünüyorum. Özellik adını dize olarak kullanmak yerine, lambda ifadesi yapmak kolaydır:

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

Marc'ın koduna aşağıdaki yöntemleri ekleyin, işinizi görecektir:

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

BTW, bu blog yayınının güncellenmiş URL'sinden ilham aldı


6
Bu yöntemi kullanan en az bir çerçeve var, ReactiveUI .
19:19

Çok geç, bu yansımadan geçiyordu, bu da bir performans isabeti anlamına geliyordu. Kabul edilebilir olabilir, ancak bir özellik ayarlamak, uygulamamın birçok döngüye harcamak istediğim bir yer değil.
Bruno Brant

1
@BrunoBrant Bir performans isabeti olduğundan emin misiniz? Blog gönderisine göre yansıma, çalışma zamanı (yani statik yansıma) yerine derleme zamanında gerçekleşir.
Nathaniel Elkins

6
Tüm OnPropertyChanged <T> cihazınızın C # 6 operatörü ile eski olduğuna ve bu canavarı biraz daha şık hale getirdiğine inanıyorum.
Traubenfuchs

5
@Traubenfuchs, aslında, C # 5'in CallerMemberName özniteliği, daha da basit hale getiriyor, çünkü hiçbir şey geçirmenize gerek yok ...
Thomas Levesque

120

Bunu yazmanıza olanak tanıyan PropertyChanged eklentisine sahip Fody de var :

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

... ve derleme sırasında özellik değişti bildirimleri enjekte eder.


7
Bu OP onlar sorulduğunda aradığı şeyi tam olarak düşünüyorum "kendimizi bizim özelliklerinde 'bildirmek' gibi bir şey uygulamak Can sınıfınızda INotifyPropertyChanged uygulamak için zarif bir çözüm var mı."
Ashoat

3
Bu gerçekten tek zarif çözüm ve @CADbloke'un dediği gibi kusursuz çalışıyor. Dokumacı hakkında da şüpheliydim, ancak IL kodunu kontrol ettim / tekrar kontrol ettim ve mükemmel, basit, ihtiyacınız olan her şeyi yapıyor ve başka hiçbir şey yapmıyor. Ayrıca, NotifyOnProp ..., OnNotify ... farketmez, temel sınıfta belirlediğiniz yöntem adını kullanırsanız, kancalar ve çağırır, bu nedenle sahip olabileceğiniz ve INotify uygulayan herhangi bir temel sınıfla iyi çalışır .. .
NSGaga-çoğunlukla-inaktif

1
Dokumacının ne yaptığını kolayca iki kez kontrol edebilir, derleme çıktı penceresine bakabilir, dokunduğu tüm PropertyChanged şeylerini listeleyebilirsiniz. Regex deseniyle VScolorOutput uzantısını kullanmak, "Fody/.*?:",LogCustom2,Trueonu "Özel 2" renginde vurgular. Parlak pembe yaptım, bu yüzden bulmak kolay. Sadece her şeyi Fody, tekrarlayan yazım çok şey yapmak için en güzel yol.
CAD bloke

@mahmoudnezarsarhan hayır, değil, yapılandırılması gereken şekilde küçük bir değişiklik olduğunu hatırlıyorum, ama Fody PropertyChanged hala canlı ve aktif.
Larry

65

Bence insanlar performansa biraz daha dikkat etmeli; bağlanması gereken çok sayıda nesne olduğunda (10.000+ satır içeren bir ızgara düşünün) veya nesnenin değeri sık sık değiştiğinde (gerçek zamanlı izleme uygulaması) kullanıcı arayüzünü gerçekten etkiler.

Burada ve başka yerlerde bulunan çeşitli uygulamaları aldım ve bir karşılaştırma yaptım; bunu kontrol INotifyPropertyChanged uygulamalarının Perfomance karşılaştırılması .


İşte sonuca bir göz atın Uygulama ve Çalışma Zamanı


14
-1: performans yükü yoktur: CallerMemberName, derleme zamanında değişmez değerlere dönüştürülür. Uygulamanızı yeniden denemeniz yeterlidir.
JYL

İşte buna göre soru ve cevap: stackoverflow.com/questions/22580623/…
uli78

1
@JYL, CallerMemberName büyük bir ek yük eklemediğini doğru. En son denediğimde yanlış bir şey uygulamış olmalıyım. Blog'u güncelleyeceğim ve daha sonra CallerMemberName ve Fody uygulaması için temel ölçütü yansıtacak cevabı vereceğim.
Peijen

1
Kullanıcı arayüzünde 10.000 + oluşan bir ızgara varsa sonra muhtemelen ... sadece sayfa başına 10, 50, 100, 250 hit göstermek çağrı gibi, sap performansına yaklaşımları birleştiren olmalıdır
Austin Rhymer

Austin Rhymer, eğer büyük veri + 50 veri sanallaştırma kullanmak varsa tüm verileri yüklemeye gerek yok sadece mevcut scolling görüntülenen alanda görünür veri yükler!
Bilal

38

Blogumda http://timoch.com/blog/2013/08/annoyed-with-inotifypropertychange/ adresindeki bir Bindable sınıfını tanıtıyorum . Bir alt sınıfın ref parametrelerini kullanarak kendi destek alanını yönetmesi için gerekli aşırı yükleri eklemek yeterince kolaydır.

  • Sihirli dize yok
  • Yansımasız
  • Varsayılan sözlük aramasını bastırmak için geliştirilebilir

Kod:

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

Bu şekilde kullanılabilir:

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

2
Bu güzel bir çözüm, ancak tek dezavantajı, boks / kutudan çıkarma ile ilgili küçük bir performans isabeti.
MCattle

1
Kullanmayı protected T Get<T>(T defaultValue, [CallerMemberName] string name = null)ve ayrıca if (_properties.ContainsKey(name) && Equals(value, Get<T>(default(T), name)))Set'i kontrol etmenizi öneririm (ilk değere varsayılan değere ayarlandığında yükseltmek ve kaydetmek için)
Miquel

1
@Miquel, özel varsayılan değerler için destek eklemek kesinlikle yararlı olabilir, ancak değiştirilen etkinliği yalnızca değer gerçekten değiştiğinde yükseltmeye dikkat etmelisiniz. Bir mülkü sahip olduğu değere ayarlamak olayları yükseltmemelidir. Çoğu durumda zararsız olduğunu itiraf etmeliyim, ancak özellikleri UI yanıtını yok eden olaylarla aynı zamana binlerce kez ayarlanmışken birkaç kez biraz oldum.
TiMoch

1
@stakx Geri alma / yineleme için hatıra desenini desteklemek veya nhibernate'in kullanılamadığı uygulamalarda çalışma deseni birimini etkinleştirmek için bunun üzerine inşa edilmiş birkaç uygulamam var
TiMoch

1
Bu özel çözümü gerçekten seviyorum: kısa gösterim, dinamik proxy şeyler yok, IL-meddling yok, vb.Ancak Get'i dinamik hale getirerek Get için her seferinde T belirtme ihtiyacını ortadan kaldırarak daha kısa yapabilirsiniz . Biliyorum, bu çalışma zamanı performansını etkiler, ancak şimdi alıcılar ve ayarlayıcılar için kod her zaman aynı olabilir ve bir satırda Rab'bi övün! Not: Değer türleri için varsayılan değerleri dinamik olarak döndürürken Get yönteminizde (temel sınıfı yazarken bir kez) daha fazla dikkat etmeniz gerekir. Her zaman doğru varsayılan değerleri döndürdüğünüzden emin olun (yapılabilir)
evilkos

15

Aslında bunu kendim denemek için bir şansım olmadı, ancak bir dahaki sefere INotifyPropertyChanged için büyük bir gereksinim olan bir proje kuruyorum Derleme zamanında kodu enjekte edecek bir Postsharp özniteliği yazmaya niyetliyim . Gibi bir şey:

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

Olacak:

private string _firstName;

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

Bunun pratikte işe yarayıp yaramayacağından emin değilim ve oturup denemem gerekiyor, ama nedenini anlamıyorum. Birden fazla OnPropertyChanged (örneğin, yukarıdaki sınıfta bir FullName özelliği vardı) tetiklenmesi gereken durumlar için bazı parametreleri kabul etmek gerekebilir

Şu anda Resharper'da özel bir şablon kullanıyorum, ancak bununla birlikte tüm özelliklerimin çok uzun sürmesinden bıktım.


Ah en az bir kişi önce böyle bir şey yaptığını gösterir (ben yazdım önce yapmalıydım) hızlı bir Google arama burada . Tam olarak aklımdakiler değil, ama teorinin iyi olduğunu gösterecek kadar yakın.


6
Fody adında ücretsiz bir araç, genel derleme zamanı kod enjektörü olarak işlev gören aynı şeyi yapıyor gibi görünüyor. Nuget'te olduğu gibi PropertyChanged ve PropertyChanging eklenti paketleri de indirilebilir.
Triynko

11

Evet, kesinlikle daha iyi bir yol var. İşte burada:

Bu faydalı makaleye dayanarak adım adım öğretici benim tarafımdan küçüldü .

  • Yeni proje oluştur
  • Kale çekirdek paketini projeye yükleyin

Kurulum Paketi Castle.Daha fazla

  • Yalnızca mvvm ışık kitaplıklarını yükleme

Kurulum Paketi MvvmLightLibs

  • Projeye iki sınıf ekleyin:

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;
    }
}
  • Görünüm modelinizi oluşturun, örneğin:

-

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

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

        public void TestAction()
        {
            Trace.WriteLine(MainTextBox);
        }
    }
  • Bağlamaları xaml içine koyun:

    <TextBox Text="{Binding MainTextBox}" ></TextBox>
    <Button Command="{Binding TestActionCommand}" >Test</Button>
  • Kod satırını MainWindow.xaml.cs kodunun arkasındaki dosyaya şu şekilde yerleştirin:

DataContext = ProxyCreator.MakeINotifyPropertyChanged<MainViewModel>();

  • Zevk almak.

resim açıklamasını buraya girin

Dikkat!!! Tüm sınırlı mülkler, kale proxy'si tarafından geçersiz kılmak için kullandıkları için sanal anahtar kelime ile dekore edilmelidir.


Hangi Castle sürümünü kullandığınızı bilmek istiyorum. Ben 3.3.0 kullanıyorum ve CreateClassProxy yöntem bu parametreleri yok: type, interfaces to apply, interceptors.
IAbstract

Boşver, genel CreateClassProxy<T>yöntemi kullanıyordum . Çok farklı ... hmmm, jenerik yöntemle neden bu kadar sınırlı olduğunu merak ediyorum. :(
IAbstract


5

Buraya bakın: http://dotnet-forum.de/blogs/thearchitect/archive/2012/11/01/die-optimale-implementierung-des-inotifypropertychanged-interfaces.aspx

Almanca yazılmıştır, ancak ViewModelBase.cs dosyasını indirebilirsiniz. Cs-File'daki tüm yorumlar İngilizce yazılmıştır.

Bu ViewModelBase Sınıfı ile, iyi bilinen Bağımlılık Özelliklerine benzer ikili düzenlenebilir özellikler uygulamak mümkündür:

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

1
Bağlantı koptu.
Guge

4

Marc'ın cevabından uyarlanan Thomas'ın cevabına dayanarak, yansıtıcı özellik değiştiren kodu bir temel sınıfa dönüştürdüm:

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

Bildirmek için ek özellikler iletebilmeniz dışında kullanım Thomas'ın cevabı ile aynıdır. Bir ızgarada yenilenmesi gereken hesaplanmış sütunları işlemek için bu gerekliydi.

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

Bu bir DataGridView yoluyla maruz bir BindingList depolanan öğeleri koleksiyonu sürüş var. Şebekeye manuel Refresh () çağrıları yapmam ihtiyacını ortadan kaldırdı.


4

Yappi adlı kendi yaklaşımımı tanıtayım . Caste Project'in Dinamik Proxy'si gibi mevcut bir nesneye veya türe yeni işlevler ekleyerek Runtime proxy | türetilmiş sınıf jeneratörlerine aittir.

INotifyPropertyChanged öğesinin temel sınıfta bir kez uygulanmasına ve türetilmiş sınıfların aşağıdaki stilde bildirilmesine izin verir, yine de yeni özellikler için INotifyPropertyChanged'i destekler:

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

Türetilmiş sınıf veya proxy yapısının karmaşıklığı aşağıdaki satırın arkasına gizlenebilir:

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

Ve tüm INotifyPropertyChanged uygulama çalışmaları şu şekilde yapılabilir:

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

Yeniden düzenleme için tamamen güvenlidir, tip yapısından sonra yansıma kullanmaz ve yeterince hızlıdır.


Neden TDeclarationtype parametresine ihtiyacınız var PropertyImplementation? Elbette sadece alıcı / ayarlayıcı aramak için (callvirt değil) uygun türü bulabilirsiniz TImplementation?
Andrew Savinykh

Uygulama çoğu durumda işe yarar. İstisnalar şunlardır: 1. Özellikler "yeni" C # keyvord ile yeniden tanımlandı. 2. Açık arayüz uygulama özellikleri.
Kelqualyn

3

Bütün bu cevaplar çok güzel.

Benim çözüm iş yapmak için kod parçacıkları kullanmaktır.

Bu, PropertyChanged olayına yapılan en basit çağrıyı kullanır.

Bu snippet'i kaydedin ve 'fullprop' snippet'ini kullanırken kullanın.

konum, Visual Studio'daki 'Araçlar \ Kod Parçacığı Yöneticisi ...' menüsünde bulunabilir.

<?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>

Aramayı istediğiniz gibi değiştirebilirsiniz (yukarıdaki çözümleri kullanmak için)


2

.NET 4.5'te dinamik kullanıyorsanız endişelenmenize gerek yoktur INotifyPropertyChanged.

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

Name bazı kontrollere bağlıysa iyi çalışır.


1
Bunu kullanmanın herhangi bir dezavantajı var mı?
juFo

2

Başka bir birleşik çözüm StackFrame kullanıyor:

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

Kullanımı:

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

2
Bu hızlı mı? Yığın çerçevesine erişim bazı izin gereksinimlerine bağlı değil mi? Async / await kullanma bağlamında sağlam mı?
Stéphane Gourichon

@ StéphaneGourichon Hayır, değil. Yığın çerçevesine erişim, çoğu durumda önemli bir performans isabeti anlamına gelir.
Bruno Brant


Satırlamanın get_Fooyöntemi Bırakma modunda gizleyebileceğini unutmayın .
bytecode77

2

Yeniden kullanmak için temel kitaplığımda bir eklenti yöntemi oluşturdum:

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

CallerMemberNameAttribute nedeniyle bu .Net 4.5 ile çalışır . Daha önceki bir .Net sürümüyle kullanmak istiyorsanız dan yöntem beyanı değiştirmek zorunda: ...,[CallerMemberName] string propertyName = "", ...hiç...,string propertyName, ...

Kullanımı:

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

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

2

Bu Yolda çözdüm (biraz zahmetli, ama kesinlikle çalışma zamanında daha hızlı).

VB (üzgünüm, ama ben C # çevirmek zor olmadığını düşünüyorum), RE ile bu ikame yapmak:

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

ile:

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

Bu transofrm tüm kodları şöyle:

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

İçinde

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

Ve daha okunabilir bir kod istiyorum, ben sadece aşağıdaki yerine yapma tersi olabilir:

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

İle

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

Set yönteminin IL kodunu değiştirmek için atıyorum, ancak IL'de çok fazla derlenmiş kod yazamıyorum ... Eğer bir gün yazarsam, size söyleyeceğim!


2

Bunu bir parçacık olarak saklıyorum. C # 6, işleyiciyi çağırmak için bazı güzel sözdizimi ekler.

// 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

NotifyPropertyChanged'in Unity3D veya CallerMemberName olmayan bir sürümü

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
}

Bu kod, aşağıdaki gibi özellik destek alanları yazmanıza olanak tanır:

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

Ayrıca, bir desen / arama snippet'i oluşturursanız yeniden birleştirmede, basit destek alanlarını yukarıdaki desteğe dönüştürerek iş akışınızı otomatikleştirebilirsiniz.

Arama Deseni:

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

Deseni Değiştir:

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

2

Bu konuda yardımcı olan bir makale yazdım ( https://msdn.microsoft.com/magazine/mt736453 ). SolSoft.DataBinding NuGet paketini kullanabilirsiniz. Sonra böyle bir kod yazabilirsiniz:

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

Yararları:

  1. temel sınıf isteğe bağlıdır
  2. her 'ayarlı değer' üzerinde yansıma yok
  3. diğer özelliklere bağlı özelliklere sahip olabilir ve bunların tümü uygun olayları otomatik olarak yükseltir (makalede bunun bir örneği vardır)

2

AOP sihirli cevapları hariç, bunu yapmanın pek çok yolu olsa da, cevapların hiçbiri, başvurmak için yerel bir alana sahip olmadan bir Modelin özelliğini doğrudan görünüm modelinden ayarlamaya bakmıyor.

Sorun, bir mülke başvuramamanızdır. Ancak, bu özelliği ayarlamak için bir Eylem kullanabilirsiniz.

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

Bu, aşağıdaki kod özütü gibi kullanılabilir.

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

Yöntemin tam olarak uygulanması ve LINQ kullanan bir yöntem ve yansıma kullanan bir yöntem de dahil olmak üzere aynı sonuca ulaşmanın birkaç farklı yolu için bu BitBucket repo'suna göz atın . Bu yöntemlerin performans açısından daha yavaş olduğunu unutmayın.


1

Bu tür özellikleri uygularken göz önünde bulundurmak isteyebileceğiniz diğer şeyler, INotifyPropertyChang * ed * in her ikisinin de olay argüman sınıflarını kullanmasıdır.

Ayarlanmış çok sayıda özelliğiniz varsa, olay bağımsız değişkeni sınıf örneği sayısı çok büyük olabilir, bunlar bir dize patlamasının olabileceği alanlardan biri olduğu için bunları önbelleğe almayı düşünmelisiniz.

Bu uygulamaya ve neden tasarlandığına bir göz atın.

Josh Smiths Blog


1

Ben sadece ActiveSharp - Otomatik INotifyPropertyChanged bulduk , henüz kullanmak için var, ama iyi görünüyor.

Web sitesinden alıntı yapmak için ...


Özellik adını dize olarak belirtmeden özellik değişikliği bildirimleri gönder.

Bunun yerine, şöyle özellikler yazın:

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

Özelliğin adını dize olarak eklemenize gerek olmadığını unutmayın. ActiveSharp bunu güvenilir ve doğru bir şekilde çözer. Mülk uygulamanızın destek alanından (_foo) ref ile geçtiği gerçeğine dayanarak çalışır. (ActiveSharp bu "ref tarafından" çağrısını hangi destek alanının iletildiğini ve alandan özelliği tanımladığını belirlemek için kullanır).


1

Yansımayı kullanan bir fikir:

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

Bu oldukça havalı, ifade yaklaşımından çok hoşuma gidiyor. Aşağı yönde, daha yavaş olmalıdır.
nawfal

1

Bu sorunun zaten bir milyar dolar cevabı olduğunu anlıyorum, ama hiçbiri benim için pek doğru hissetmedi. Benim sorunum, herhangi bir performans isabeti istemiyorum ve sadece bu nedenle küçük bir ayrıntı katmaya hazırım. Ayrıca otomatik özellikleri de çok fazla umursamıyorum, bu da beni aşağıdaki çözüme götürdü:

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

Başka bir deyişle, bunu yapmak sakıncası yoksa yukarıdaki çözüm uygundur:

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

Artıları

  • Yansımasız
  • Yalnızca eski değer! = Yeni değer
  • Aynı anda birden fazla mülke bildirim gönder

Eksileri

  • Otomatik özellik yok (her ikisi için de destek ekleyebilirsiniz!)
  • Bazı ayrıntılar
  • Boks (küçük performans isabet?)

Ne yazık ki, bunu yapmaktan daha iyidir,

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

Ek ayrıntılarla kabus haline gelen her bir mülk için ;-(

Bu çözümün diğerlerine kıyasla daha iyi performans açısından olduğunu iddia etmiyorum, sadece sunulan diğer çözümleri sevmeyenler için uygun bir çözüm olduğunu iddia etmiyorum.


1

Gözlenebilir kalıbı uygulamak için bu temel sınıf ile geldim, neredeyse ihtiyacınız olanı ( seti otomatik olarak uygulamak ve almak) yapar. Prototip olarak bir saat harcadım, bu yüzden çok fazla birim testi yok, ancak konsepti kanıtlıyor. Dictionary<string, ObservablePropertyContext>Özel alanlara olan ihtiyacı ortadan kaldırmak için şunu kullandığını unutmayın .

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

İşte kullanımı

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

ReactiveProperty kullanmanızı öneririm. Bu, Fody hariç en kısa yöntemdir.

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

yerine

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

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

( DOCS )


0

Diğer bir fikir...

 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

=> burada aşağıdaki özelliklere sahip çözümüm

 public ResourceStatus Status
 {
     get { return _status; }
     set
     {
         _status = value;
         Notify(Npcea.Status,Npcea.Comments);
     }
 }
  1. refelction yok
  2. kısa gösterim
  3. işletme kodunuzda sihirli bir dize yok
  4. PropertyChangedEventArgs'ın uygulama genelinde yeniden kullanılabilirliği
  5. Bir ifadede birden çok özelliği bildirme imkanı

0

Bunu kullan

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

}

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.