Menerapkan INotifyPropertyChanged - apakah ada cara yang lebih baik?

647

Microsoft seharusnya menerapkan sesuatu yang cepat INotifyPropertyChanged, seperti pada properti otomatis, sebutkan saja {get; set; notify;} saya pikir itu masuk akal untuk melakukannya. Atau adakah komplikasi untuk melakukannya?

Bisakah kita menerapkan sesuatu seperti 'beri tahu' di properti kita. Apakah ada solusi yang baik untuk diterapkan INotifyPropertyChangeddi kelas Anda atau satu-satunya cara untuk melakukannya adalah dengan meningkatkan PropertyChangedacara di setiap properti.

Jika tidak, bisakah kita menulis sesuatu untuk menghasilkan potongan kode secara otomatis untuk meningkatkan PropertyChanged acara?

PK
sumber
7
code.google.com/p/notifypropertyweaver mungkin berguna
Ian Ringrose
7
tautan di atas sudah mati. github.com/SimonCropp/NotifyPropertyWeaver
prime23
2
Anda bisa menggunakan DependencyObject dan DependencyProperties sebagai gantinya. HA! Saya membuat lucu.
Phil
5
Pada saat itu membuat perubahan ke C # tidak mungkin mengingat kami memiliki log kembali besar antar-dependensi. Jadi ketika MVVM lahir saya kira, kami hanya benar-benar tidak berusaha keras untuk menyelesaikan masalah ini dan saya tahu tim Pola & Praktek telah melakukan beberapa hal di sepanjang jalan (maka Anda juga mendapat MEF sebagai bagian dari itu utas penelitian). Hari ini saya pikir [CallerMemberName] adalah jawaban di atas.
Scott Barnes

Jawaban:

633

Tanpa menggunakan sesuatu seperti postsharp, versi minimal yang saya gunakan menggunakan sesuatu seperti:

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

Setiap properti kemudian hanya sesuatu seperti:

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

yang tidak besar; itu juga dapat digunakan sebagai kelas dasar jika Anda mau. The boolkembali dari SetFieldmemberitahu Anda jika itu adalah no-op, jika Anda ingin menerapkan logika lain.


atau lebih mudah dengan C # 5:

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

yang bisa disebut seperti ini:

set { SetField(ref name, value); }

dengan mana kompiler akan menambahkan "Name"secara otomatis.


C # 6.0 membuat implementasi lebih mudah:

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

... dan sekarang dengan 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);
}
Marc Gravell
sumber
4
Trik yang bagus, Marc! Saya menyarankan perbaikan untuk menggunakan ekspresi lambda, bukan nama properti, lihat jawaban saya
Thomas Levesque
7
@ Thomas - lambda baik-baik saja, tetapi menambahkan banyak overhead untuk sesuatu yang sebenarnya sangat sederhana. Trik yang berguna, tapi saya tidak yakin itu selalu praktis.
Marc Gravell
14
@ Markc - Ya, itu mungkin dapat menurunkan kinerja ... Namun saya benar-benar menyukai kenyataan bahwa itu diperiksa pada waktu kompilasi, dan secara benar dire-refored oleh perintah "Ganti nama"
Thomas Levesque
4
@ Goddor untungnya, dengan C # 5 tidak perlu berkompromi - Anda bisa mendapatkan yang terbaik dari keduanya melalui (seperti catatan Pedro77)[CallerMemberName]
Marc Gravell
4
@ Goddor bahasa dan kerangka kerjanya terpisah; Anda dapat menggunakan kompiler C # 5, target .NET 4, dan tambahkan saja atribut yang hilang sendiri - itu akan berfungsi dengan baik. Itu hanya harus memiliki nama yang benar dan berada di namespace yang benar. Tidak perlu berada di majelis khusus.
Marc Gravell
196

Pada .Net 4.5 akhirnya ada cara mudah untuk melakukan ini.

.Net 4.5 memperkenalkan Atribut Informasi Penelepon baru.

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

Mungkin juga merupakan ide bagus untuk menambahkan pembanding ke fungsi tersebut.

EqualityComparer<T>.Default.Equals

Lebih banyak contoh di sini dan di sini

Juga lihat Informasi Penelepon (C # dan Visual Basic)

Daniel Little
sumber
12
Cemerlang! Tapi mengapa itu generik?
abatishchev
@abatishchev Saya rasa itu tidak harus, saya hanya bermain dengan gagasan memiliki fungsi mengatur properti juga. Saya akan melihat apakah saya dapat memperbarui jawaban saya memberikan solusi lengkap. Contoh-contoh tambahan melakukan pekerjaan yang baik sementara itu.
Daniel Little
3
Itu diperkenalkan oleh C # 5.0. Ini tidak ada hubungannya dengan .net 4.5, tetapi ini adalah solusi yang bagus!
J. Lennon
5
@J. Lennon .net 4.5 masih ada hubungannya dengan itu, setelah semua atribut berasal dari suatu tempat msdn.microsoft.com/en-au/library/…
Daniel Little
@Lavinski mengubah aplikasi Anda menjadi .NET 3.5 dan lihat apa yang akan bekerja (dalam vs2012)
J. Lennon
162

Saya sangat suka solusi Marc, tapi saya pikir itu bisa sedikit ditingkatkan untuk menghindari menggunakan "string ajaib" (yang tidak mendukung refactoring). Alih-alih menggunakan nama properti sebagai string, lebih mudah menjadikannya ekspresi lambda:

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

Cukup tambahkan metode berikut ke kode Marc, itu akan melakukan trik:

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, ini terinspirasi oleh posting blog ini URL yang diperbarui

Thomas Levesque
sumber
6
Setidaknya ada satu kerangka kerja yang menggunakan metode ini, ReactiveUI .
AlSki
Sangat terlambat, ini berarti harus melalui refleksi, yang berarti kinerja yang baik. Itu bisa diterima, tetapi menetapkan properti bukan tempat di mana saya ingin aplikasi saya habiskan untuk banyak siklus.
Bruno Brant
1
@ BrunoBrant Apakah Anda yakin ada performa yang mengejutkan? Menurut posting blog refleksi terjadi selama waktu kompilasi daripada runtime (yaitu refleksi statis).
Nathaniel Elkins
6
Saya percaya seluruh OnPropertyChanged <T> Anda sudah usang dengan nama operator C # 6, membuat monster ini sedikit lebih ramping.
Traubenfuchs
5
@Traubenfuchs, sebenarnya, atribut CallerMemberName C # 5 membuatnya lebih sederhana, karena Anda tidak perlu melewatkan apa pun ...
Thomas Levesque
120

Ada juga Fody yang memiliki add-in PropertyChanged , yang memungkinkan Anda menulis ini:

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

... dan pada waktu kompilasi menyuntikkan pemberitahuan properti yang diubah.

Tom Gilder
sumber
7
Saya pikir inilah yang dicari OP ketika mereka bertanya, "Bisakah kita menerapkan sesuatu seperti 'beri tahu' di properti kita. Apakah ada solusi yang baik untuk mengimplementasikan INotifyProperty yang diubah di kelas Anda"
Ashoat
3
Ini adalah satu-satunya solusi yang benar-benar anggun, dan ini berfungsi dengan sempurna seperti yang dikatakan @CADbloke. Dan saya skeptis tentang penenun juga, tapi saya memeriksa / mengecek ulang kode IL di belakang dan itu sempurna, sangat sederhana, melakukan semua yang Anda butuhkan dan tidak ada yang lain. Itu juga mengaitkan dan memanggil nama metode apa pun yang telah Anda tetapkan di kelas dasar untuk itu, apakah NotifyOnProp ..., OnNotify ... tidak masalah, jadi bekerja dengan baik dengan kelas dasar apa pun yang mungkin Anda miliki dan yang mengimplementasikan INotify .. .
NSGaga-sebagian besar-aktif
1
Anda dapat dengan mudah memeriksa ulang apa yang dilakukan penenun, lihatlah jendela output build, daftar semua propertiChanged yang telah ditenun. Menggunakan ekstensi VScolorOutput dengan pola regex "Fody/.*?:",LogCustom2,Truemenyoroti dalam warna "Custom 2". Saya membuatnya merah muda cerah sehingga mudah ditemukan. Hanya Fody segalanya, ini adalah cara paling rapi untuk melakukan apa pun yang memiliki banyak pengetikan berulang.
CAD berbicara
@mahmoudnezarsarhan tidak, tidak, saya ingat ada sedikit perubahan dalam cara mengkonfigurasi, tetapi Fody PropertyChanged masih hidup dan aktif.
Larry
65

Saya pikir orang harus lebih memperhatikan kinerja; itu benar-benar berdampak pada UI ketika ada banyak objek untuk diikat (bayangkan sebuah grid dengan 10.000 baris), atau jika nilai objek sering berubah (aplikasi pemantauan real-time).

Saya mengambil berbagai implementasi yang ditemukan di sini dan di tempat lain dan melakukan perbandingan; lihat perbandingan kinerja implementasi INotifyPropertyChanged .


Berikut ini intip hasilnya Implemenasi vs Runtime

Peijen
sumber
14
-1: tidak ada overhead kinerja: CallerMemberName diubah menjadi nilai literal pada waktu kompilasi. Coba dan dekompilasi aplikasi Anda.
JYL
berikut adalah pertanyaan dan jawaban yang sesuai: stackoverflow.com/questions/22580623/…
uli78
1
@ JYL, Anda benar bahwa CallerMemberName tidak menambahkan overhead yang besar. Saya pasti telah mengimplementasikan sesuatu yang salah saat terakhir saya mencobanya. Saya akan memperbarui blog dan menjawab untuk mencerminkan tolok ukur untuk implementasi CallerMemberName dan Fody nanti.
Peijen
1
Jika Anda memiliki kisi 10.000+ di UI maka Anda mungkin harus menggabungkan pendekatan untuk menangani kinerja, seperti paging di mana Anda hanya menampilkan 10, 50, 100, 250 klik per halaman ...
Austin Rhymer
Austin Rhymer, jika Anda memiliki data massal + 50 gunakan virtualisasi data tidak perlu memuat semua data, itu hanya akan memuat data yang terlihat pada area yang ditampilkan saat ini!
Bilal
38

Saya memperkenalkan kelas Bindable di blog saya di http://timoch.com/blog/2013/08/annoyed-with-inotifypropertychange/ Bindable menggunakan kamus sebagai tas properti. Cukup mudah untuk menambahkan kelebihan yang diperlukan untuk subkelas untuk mengelola bidang dukungannya sendiri menggunakan parameter ref.

  • Tidak ada benang ajaib
  • Tidak ada refleksi
  • Dapat ditingkatkan untuk menekan pencarian kamus default

Kode:

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

Dapat digunakan seperti ini:

public class Contact : Bindable {
    public string FirstName {
        get { return Get<string>(); }
        set { Set(value); }
    }
}
TiMoch
sumber
2
Ini adalah solusi yang bagus, tetapi satu-satunya downside adalah bahwa ada kinerja kecil yang melibatkan tinju / unboxing
MCattle
1
Saya akan menyarankan untuk menggunakan protected T Get<T>(T defaultValue, [CallerMemberName] string name = null)dan juga memeriksa if (_properties.ContainsKey(name) && Equals(value, Get<T>(default(T), name)))di Set (untuk meningkatkan & menyimpan ketika pertama kali diatur ke nilai default)
Miquel
1
@Miquel menambahkan dukungan untuk nilai-nilai standar kustom dapat berguna pasti, namun Anda harus berhati-hati untuk hanya meningkatkan acara yang diubah ketika nilainya benar-benar berubah. Menyetel properti ke nilai yang sama seharusnya tidak meningkatkan kejadian. Saya harus mengakui dalam banyak kasus itu tidak berbahaya, namun saya sudah beberapa kali melakukan beberapa properti dengan nilai ribuan kali untuk nilai yang sama dengan peristiwa yang menghancurkan respon UI.
TiMoch
1
@stakx Saya punya beberapa aplikasi yang membangun ini untuk mendukung pola kenang-kenangan untuk undo / redo atau untuk mengaktifkan unit pola kerja dalam aplikasi di mana nhibernate tidak dapat digunakan
TiMoch
1
Saya sangat suka solusi khusus ini: notasi singkat, tidak ada proksi dinamis, tidak ada campur tangan IL, dll. Meskipun, Anda dapat membuatnya lebih pendek dengan menghapus kebutuhan untuk menentukan T setiap waktu untuk Dapatkan dengan membuat Dapatkan kembali dinamis. Saya tahu, ini memengaruhi kinerja runtime, tetapi sekarang kode untuk getter dan setter akhirnya dapat selalu sama dan dalam satu baris , puji Tuhan! PS Anda harus lebih berhati-hati dalam metode Get Anda (satu kali ketika Anda menulis kelas dasar) ketika mengembalikan nilai default untuk nilai jenis sebagai dinamis. Pastikan untuk selalu mengembalikan nilai default yang benar (dapat dilakukan)
evilkos
15

Saya belum benar-benar memiliki kesempatan untuk mencobanya sendiri, tetapi lain kali saya membuat proyek dengan persyaratan besar untuk INotifyPropertyChanged Saya berniat menulis atribut Postsharp yang akan menyuntikkan kode pada waktu kompilasi. Sesuatu seperti:

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

Akan menjadi:

private string _firstName;

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

Saya tidak yakin apakah ini akan berhasil dalam praktek dan saya perlu duduk dan mencobanya, tetapi saya tidak mengerti mengapa tidak. Saya mungkin perlu membuatnya menerima beberapa parameter untuk situasi di mana lebih dari satu OnPropertyChanged perlu dipicu (jika, misalnya, saya memiliki properti FullName di kelas di atas)

Saat ini saya menggunakan template khusus di Resharper, tetapi bahkan dengan itu saya sudah muak dengan semua properti saya yang begitu lama.


Ah, pencarian Google cepat (yang seharusnya saya lakukan sebelum saya menulis ini) menunjukkan bahwa setidaknya satu orang telah melakukan sesuatu seperti ini sebelumnya sini . Tidak persis apa yang ada dalam pikiran saya, tetapi cukup dekat untuk menunjukkan bahwa teorinya baik.

Martin Harris
sumber
6
Alat gratis bernama Fody tampaknya melakukan hal yang sama, berfungsi sebagai injektor kode waktu kompilasi umum. Ini dapat diunduh di Nuget, seperti paket plugin PropertyChanged dan PropertyChanging.
Triynko
11

Ya, cara yang lebih baik tentu ada. Ini dia:

Tutorial langkah demi langkah menyusut oleh saya, berdasarkan ini artikel yang bermanfaat ini .

  • Buat proyek baru
  • Instal paket inti kastil ke dalam proyek

Instal-Paket Castle.Core

  • Instal perpustakaan cahaya mvvm saja

Instal-Paket MvvmLightLibs

  • Tambahkan dua kelas dalam proyek:

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;
    }
}
  • Buat model tampilan Anda, misalnya:

-

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

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

        public void TestAction()
        {
            Trace.WriteLine(MainTextBox);
        }
    }
  • Masukkan binding ke dalam xaml:

    <TextBox Text="{Binding MainTextBox}" ></TextBox>
    <Button Command="{Binding TestActionCommand}" >Test</Button>
  • Masukkan baris kode dalam file kode-belakang MainWindow.xaml.cs seperti ini:

DataContext = ProxyCreator.MakeINotifyPropertyChanged<MainViewModel>();

  • Nikmati.

masukkan deskripsi gambar di sini

Perhatian!!! Semua properti yang dibatasi harus dihiasi dengan kata kunci virtual karena mereka digunakan oleh proxy castle untuk menimpa.

testCoder
sumber
Saya tertarik untuk mengetahui versi Castle yang Anda gunakan. Saya menggunakan 3.3.0 dan metode CreateClassProxy tidak memiliki parameter tersebut: type, interfaces to apply, interceptors.
IAbstract
Nevermind, saya menggunakan CreateClassProxy<T>metode generik . Jauh berbeda ... hmmm, bertanya-tanya mengapa begitu terbatas dengan metode generik. :(
IAbstract
7

Pendekatan yang sangat mirip AOP adalah menyuntikkan barang-barang INotifyPropertyChanged ke objek yang sudah dipakai dengan cepat. Anda dapat melakukan ini dengan sesuatu seperti Castle DynamicProxy. Berikut ini adalah artikel yang menjelaskan teknik ini:

Menambahkan INotifyPropertyChanged ke objek yang ada

HokieMike
sumber
5

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

Ini ditulis dalam bahasa Jerman, tetapi Anda dapat mengunduh ViewModelBase.cs. Semua komentar dalam File cs ditulis dalam Bahasa Inggris.

Dengan ViewModelBase-Class ini dimungkinkan untuk mengimplementasikan properti bindable yang mirip dengan Properti Ketergantungan yang terkenal:

public string SomeProperty
{
    get { return GetValue( () => SomeProperty ); }
    set { SetValue( () => SomeProperty, value ); }
}
DotNetMastermind
sumber
1
Tautan rusak.
Guge
4

Berdasarkan jawaban oleh Thomas yang diadaptasi dari jawaban oleh Marc, saya telah mengubah kode perubahan properti yang direfleksikan menjadi kelas dasar:

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

Penggunaannya sama dengan jawaban Thomas kecuali Anda dapat memberikan properti tambahan untuk diberitahukan. Ini diperlukan untuk menangani kolom terhitung yang perlu di-refresh dalam kotak.

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

Saya punya ini mengemudi koleksi item yang disimpan di BindingList terbuka melalui DataGridView. Ini telah menghilangkan kebutuhan saya untuk melakukan panggilan Refresh () manual ke grid.

rev StuffOfInterest
sumber
4

Biarkan saya memperkenalkan pendekatan saya sendiri yang disebut Yappi . Itu milik generator kelas turunan proxy Runtime |, menambahkan fungsionalitas baru ke objek atau tipe yang ada, seperti Proxy Dinamis dari Caste Project.

Ini memungkinkan untuk mengimplementasikan INotifyPropertyChanged sekali di kelas dasar, dan kemudian mendeklarasikan kelas turunan dengan gaya berikut, masih mendukung INotifyPropertyChanged untuk properti baru:

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

Kompleksitas kelas turunan atau konstruksi proxy dapat disembunyikan di belakang baris berikut:

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

Dan semua pekerjaan implementasi INotifyPropertyChanged dapat dilakukan seperti ini:

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

Ini sepenuhnya aman untuk refactoring, tidak menggunakan pantulan setelah konstruksi tipe dan cukup cepat.

Kelqualyn
sumber
Mengapa Anda perlu TDeclarationmengetikkan parameter PropertyImplementation? Tentunya Anda dapat menemukan tipe yang tepat untuk memanggil (bukan callvirt) pengambil / penyetel hanya dengan TImplementation?
Andrew Savinykh
Penerapan TI berfungsi dalam sebagian besar kasus. Pengecualian adalah: 1. Properti didefinisikan ulang dengan keyvord C # baru. 2. Properti implementasi antarmuka eksplisit.
Kelqualyn
3

Semua jawaban ini sangat bagus.

Solusi saya menggunakan cuplikan kode untuk melakukan pekerjaan.

Ini menggunakan panggilan paling sederhana ke acara PropertyChanged.

Simpan cuplikan ini dan gunakan saat Anda menggunakan cuplikan 'fullprop'.

lokasi dapat ditemukan di menu 'Tools \ Code Snippet Manager ...' di 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>

Anda dapat memodifikasi panggilan sesuka Anda (untuk menggunakan solusi di atas)

Ofir
sumber
2

Jika Anda menggunakan dinamika di .NET 4.5, Anda tidak perlu khawatir INotifyPropertyChanged.

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

jika Name terikat ke beberapa kontrol, itu hanya berfungsi dengan baik.

Dilshod
sumber
1
Kerugian menggunakan ini?
juFo
2

Solusi gabungan lainnya menggunakan 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));
        }
    }
}

Pemakaian:

public class TempVM : BaseViewModel
{
    private int _intP;
    public int IntP
    {
        get { return _intP; }
        set { Set<int>(ref _intP, value); }
    }
}
Ofir
sumber
2
Apakah itu cepat? Tidakkah akses ke tumpukan bingkai terikat pada beberapa persyaratan izin? Apakah itu kuat dalam konteks menggunakan async / menunggu?
Stéphane Gourichon
@ StéphaneGourichon Tidak, tidak. Mengakses bingkai tumpukan berarti kinerja yang luar biasa pada kebanyakan kasus.
Bruno Brant
Ya ada, Anda dapat melihatnya di codereview.stackexchange.com/questions/13823/…
Ofir
Perhatikan bahwa inlining dapat menyembunyikan get_Foometode dalam mode Release.
bytecode77
2

Saya membuat Metode Ekstensi di Perpustakaan basis saya untuk digunakan kembali:

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

Ini berfungsi dengan .Net 4.5 karena CallerMemberNameAttribute . Jika Anda ingin menggunakannya dengan versi .Net yang lebih lama, Anda harus mengubah deklarasi metode dari:...,[CallerMemberName] string propertyName = "", ... menjadi...,string propertyName, ...

Pemakaian:

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

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

Saya memutuskan dengan cara ini (ini sedikit laboriouse, tapi itu pasti lebih cepat di runtime).

Di VB (maaf, tapi saya pikir tidak sulit menerjemahkannya dalam C #), saya membuat substitusi ini dengan RE:

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

dengan:

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

Ini transofrm semua kode seperti ini:

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

Di

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

Dan Jika saya ingin memiliki kode yang lebih mudah dibaca, saya bisa sebaliknya hanya membuat substitusi berikut:

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

Dengan

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

Saya membuang untuk mengganti kode IL dari metode yang ditetapkan, tetapi saya tidak bisa menulis banyak kode yang dikompilasi di IL ... Jika suatu hari saya menulisnya, saya akan mengatakan Anda!

Lucio Menci
sumber
2

Saya menyimpan ini sebagai potongan. C # 6 menambahkan beberapa sintaks yang bagus untuk memanggil pawang.

// 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));
    }
}
Mike Ward
sumber
2

Berikut adalah NotifyPropertyChanged versi Unity3D atau non-CallerMemberName

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
}

Kode ini memungkinkan Anda untuk menulis bidang dukungan properti seperti ini:

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

Selain itu, dalam pengambilan ulang jika Anda membuat potongan pola / pencarian, Anda kemudian dapat juga mengotomatiskan alur kerja Anda dengan mengonversi bidang prop sederhana menjadi dukungan di atas.

Pola pencarian:

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

Ganti Pola:

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

Saya telah menulis artikel yang membantu ini ( https://msdn.microsoft.com/magazine/mt736453 ). Anda dapat menggunakan paket NuGet SolSoft.DataBinding. Maka Anda dapat menulis kode seperti ini:

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

Manfaat:

  1. kelas dasar adalah opsional
  2. tidak ada refleksi pada setiap 'nilai yang ditetapkan'
  3. dapat memiliki properti yang bergantung pada properti lain, dan mereka semua secara otomatis meningkatkan acara yang sesuai (artikel memiliki contohnya)
Mark Sowul
sumber
2

Walaupun jelas ada banyak cara untuk melakukan ini, dengan pengecualian jawaban sihir AOP, tidak ada jawaban yang tampaknya melihat pengaturan properti Model langsung dari model tampilan tanpa memiliki bidang lokal untuk referensi.

Masalahnya adalah Anda tidak dapat mereferensikan properti. Namun, Anda bisa menggunakan Action untuk menyetel properti itu.

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

Ini dapat digunakan seperti ekstrak kode berikut.

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

Lihat repo BitBucket ini untuk implementasi penuh metode dan beberapa cara berbeda untuk mencapai hasil yang sama, termasuk metode yang menggunakan LINQ dan metode yang menggunakan refleksi. Perhatikan bahwa metode ini lebih lambat kinerjanya.

Dan
sumber
1

Hal-hal lain yang mungkin ingin Anda pertimbangkan ketika menerapkan sifat-sifat ini adalah kenyataan bahwa INotifyPropertyChang * ed * ing keduanya menggunakan kelas argumen acara.

Jika Anda memiliki sejumlah besar properti yang sedang disetel maka jumlah instance kelas argumen event bisa sangat besar, Anda harus mempertimbangkan untuk menyimpannya karena itu adalah salah satu area yang dapat terjadi ledakan string.

Lihatlah implementasi ini dan penjelasan mengapa itu dikandung.

Josh Smiths Blog

Peter
sumber
1

Saya baru saja menemukan ActiveSharp - Automatic INotifyPropertyChanged , saya belum menggunakannya, tetapi terlihat bagus.

Mengutip dari situs webnya ...


Kirim pemberitahuan perubahan properti tanpa menyebutkan nama properti sebagai string.

Alih-alih, tulis properti seperti ini:

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

Perhatikan bahwa tidak perlu menyertakan nama properti sebagai string. ActiveSharp secara andal dan benar memperkirakan hal itu untuk dirinya sendiri. Ini bekerja berdasarkan fakta bahwa implementasi properti Anda melewati bidang dukungan (_foo) oleh ref. (ActiveSharp menggunakan panggilan "by ref" untuk mengidentifikasi bidang dukungan mana yang dilewati, dan dari bidang itu mengidentifikasi properti).

Ian Ringrose
sumber
1

Sebuah ide menggunakan refleksi:

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
Mendongkrak
sumber
Ini sangat keren, saya lebih menyukainya daripada pendekatan ekspresi. Pada sisi negatifnya, harus lebih lambat.
nawfal
1

Saya menyadari pertanyaan ini sudah memiliki trilyun jawaban, tetapi tidak satupun dari mereka merasa cukup tepat untuk saya. Masalah saya adalah saya tidak ingin ada hit kinerja dan saya mau tahan dengan sedikit kata-kata kasar untuk alasan itu saja. Saya juga tidak terlalu peduli untuk properti otomatis, yang membawa saya ke solusi berikut:

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

Dengan kata lain, solusi di atas nyaman jika Anda tidak keberatan melakukan ini:

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

Pro

  • Tidak ada refleksi
  • Hanya beri tahu jika nilai lama! = Nilai baru
  • Beri tahu beberapa properti sekaligus

Cons

  • Tidak ada properti otomatis (Anda dapat menambahkan dukungan untuk keduanya!)
  • Beberapa kata
  • Tinju (hit kinerja kecil?)

Sayangnya, masih lebih baik daripada melakukan ini,

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

Untuk setiap properti, yang menjadi mimpi buruk dengan verbositas tambahan ;-(

Catatan, saya tidak mengklaim solusi ini lebih baik dari segi kinerja dibandingkan dengan yang lain, hanya saja itu adalah solusi yang layak bagi mereka yang tidak menyukai solusi lain yang disajikan.

James M
sumber
1

Saya datang dengan kelas dasar ini untuk menerapkan pola yang dapat diamati, cukup banyak melakukan apa yang Anda butuhkan ( "secara otomatis" menerapkan set dan dapatkan). Saya menghabiskan satu jam pada ini sebagai prototipe, jadi tidak memiliki banyak unit test, tetapi membuktikan konsepnya. Catatan itu menggunakan Dictionary<string, ObservablePropertyContext>untuk menghapus kebutuhan untuk bidang pribadi.

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

Ini penggunaannya

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); }
    }
  }
Homero Barbosa
sumber
1

Saya sarankan untuk menggunakan ReactiveProperty. Ini adalah metode terpendek kecuali Fody.

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

sebagai gantinya

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

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

( DOCS )

soi
sumber
0

Ide lain ...

 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));
    }
}
Toro
sumber
0

=> di sini solusi saya dengan fitur-fitur berikut

 public ResourceStatus Status
 {
     get { return _status; }
     set
     {
         _status = value;
         Notify(Npcea.Status,Npcea.Comments);
     }
 }
  1. tidak ada penolakan
  2. notasi singkat
  3. tidak ada string ajaib dalam kode bisnis Anda
  4. Dapat digunakan kembali PropertiChangedEventArgs di seluruh aplikasi
  5. Kemungkinan untuk memberi tahu beberapa properti dalam satu pernyataan
Bruno
sumber
0

Gunakan ini

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

}

Dude505
sumber