INotifyPropertyChanged को लागू करना - क्या बेहतर तरीका मौजूद है?


647

Microsoft को INotifyPropertyChangedस्वत: गुणों की तरह कुछ तेज़ लागू करना चाहिए था , बस {get; set; notify;} मुझे लगता है कि इसे करने के लिए बहुत कुछ समझ में आता है। या इसे करने के लिए कोई जटिलताएं हैं?

क्या हम स्वयं अपने गुणों में 'अधिसूचित' जैसा कुछ लागू कर सकते हैं। क्या INotifyPropertyChangedआपकी कक्षा में लागू करने के लिए एक सुंदर समाधान है या ऐसा करने का एकमात्र तरीका PropertyChangedप्रत्येक संपत्ति में घटना को बढ़ाकर है ।

यदि हम PropertyChanged घटना को बढ़ाने के लिए कोड के टुकड़े को ऑटो-जेनरेट करने के लिए कुछ नहीं लिख सकते हैं ?


7
code.google.com/p/notifypropertyweaver उपयोग का हो सकता है
इयान रिंगरोज

7
ऊपर लिंक मर चुका है। github.com/SimonCropp/NotifyPropertyWeaver
प्रधानमंत्री

2
आप इसके बजाय DependencyObject और DependencyProperties का उपयोग कर सकते हैं। हा! मैंने एक मजाक बनाया।
फिल


5
सी # में परिवर्तन करते समय यह संभव नहीं था क्योंकि हमें अंतर-निर्भरता का एक बड़ा लॉग था। इसलिए जब एमवीवीएम का जन्म हुआ तो मुझे लगता है कि हम वास्तव में इस मुद्दे को सुलझाने में ज्यादा प्रयास नहीं करते थे और मुझे पता है कि पैटर्न एंड प्रैक्टिसेस टीम ने कुछ इस तरह से साथ किया था (इसलिए आपको भी इसके हिस्से के रूप में एमईएफ मिला था शोध सूत्र)। आज मुझे लगता है कि [CallerMemberName] ऊपर का जवाब है।
स्कॉट बार्न्स

जवाबों:


633

Postharp जैसी किसी चीज़ का उपयोग किए बिना, मेरे द्वारा उपयोग किया जाने वाला न्यूनतम संस्करण कुछ इस तरह का उपयोग करता है:

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

प्रत्येक संपत्ति तो कुछ इस तरह है:

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

जो बहुत बड़ा नहीं है; यदि आप चाहें तो इसे बेस-क्लास के रूप में भी उपयोग किया जा सकता है। boolसे लौटने SetFieldआपको बताता है कि अगर यह एक नहीं सेशन था, मामले में आप अन्य तर्क लागू करना चाहते हैं।


या C # 5 के साथ और भी आसान:

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

जिसे इस तरह कहा जा सकता है:

set { SetField(ref name, value); }

जिसके साथ संकलक जोड़ देगा "Name" स्वचालित रूप ।


C # 6.0 कार्यान्वयन को आसान बनाता है:

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

... और अब C # 7 के साथ:

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

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

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

4
अच्छी चाल मार्क! मैंने संपत्ति के नाम के बजाय एक लंबोदर अभिव्यक्ति का उपयोग करने के लिए एक सुधार का सुझाव दिया, मेरा जवाब देखें
थॉमस लेवेस्क

7
@ थोमस - लंबोदर सब कुछ ठीक है और अच्छा है, लेकिन यह बहुत कुछ है जो वास्तव में बहुत सरल है के लिए ओवरहेड जोड़ता है। एक आसान चाल, लेकिन मुझे यकीन नहीं है कि यह हमेशा व्यावहारिक है।
मार्क Gravell

14
@Marc - हां, यह संभवतः प्रदर्शन को नीचा दिखा सकता है ... हालाँकि मुझे वास्तव में यह पसंद है कि यह संकलन समय पर जांचा जाता है, और "Rename" कमांड द्वारा सही ढंग से रिफ्लेक्ट किया गया है
थॉमस लेवेस्क

4
@Gusdor सौभाग्य से, सी # 5 के साथ वहाँ समझौता करने की कोई जरूरत है - आप के माध्यम से दोनों का सबसे अच्छा प्राप्त कर सकते हैं (Pedro77 नोटों के रूप में)[CallerMemberName]
मार्क Gravell

4
@Gusdor भाषा और रूपरेखा अलग हैं; आप C # 5 संकलक का उपयोग कर सकते हैं, .NET 4 को लक्षित कर सकते हैं, और केवल अनुपलब्ध विशेषता को स्वयं जोड़ सकते हैं - यह ठीक काम करेगा। इसके लिए सिर्फ सही नाम होना चाहिए और सही नाम स्थान पर होना चाहिए। इसे एक विशिष्ट विधानसभा में होने की आवश्यकता नहीं है।
मार्क Gravell

196

.Net 4.5 के रूप में आखिरकार ऐसा करने का एक आसान तरीका है।

.Net 4.5 एक नया कॉलर सूचना गुण प्रस्तुत करता है।

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

यह फ़ंक्शन के साथ तुलना करने के लिए एक अच्छा विचार है।

EqualityComparer<T>.Default.Equals

और अधिक उदाहरण यहाँ और यहाँ

यह भी देखें कोलर सूचना (सी # और विजुअल बेसिक)


12
प्रतिभाशाली! लेकिन यह सामान्य क्यों है?
abatishchev

@abatishchev मुझे लगता है कि यह होना जरूरी नहीं है, मैं सिर्फ समारोह के साथ ही संपत्ति सेट करने के विचार के साथ खेल रहा था। मैं देखूंगा कि क्या मैं अपना जवाब अपडेट कर सकता हूं पूर्ण समाधान प्रदान करें। अतिरिक्त उदाहरण एक अच्छा काम करते हैं कि इस बीच।
डैनियल लिटिल

3
इसे C # 5.0 द्वारा पेश किया गया था। यह .net 4.5 के साथ कुछ नहीं करना है, लेकिन यह एक महान समाधान है!
जे। लेनन

5
@जे। लेनन .net 4.5 अभी भी इसके साथ कुछ करना है, सभी गुण कहीं से आने के बाद msdn.microsoft.com/en-au/library/…
डैनियल लिटिल

@Lavinski ने अपने एप्लिकेशन को उदाहरण के लिए .NET 3.5 में बदल दिया और देखें कि क्या काम करेगा (vs2012 में)
जे। लेनन

162

मुझे वास्तव में मार्क का समाधान पसंद है, लेकिन मुझे लगता है कि "मैजिक स्ट्रिंग" (जो रीफैक्टरिंग का समर्थन नहीं करता) का उपयोग करने से बचने के लिए इसे थोड़ा बेहतर बनाया जा सकता है। एक स्ट्रिंग के रूप में संपत्ति के नाम का उपयोग करने के बजाय, इसे लंबोदर अभिव्यक्ति बनाना आसान है:

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

बस मार्क के कोड में निम्नलिखित विधियाँ जोड़ें, यह चाल चलेगा:

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, यह इस ब्लॉग पोस्ट अपडेटेड URL से प्रेरित था


6
इस विधि का उपयोग करते हुए कम से कम एक ढांचा है, रिएक्टिव्यू
अलसी

बहुत देर से, इसका मतलब प्रतिबिंब से गुजरना था, जिसका मतलब था एक प्रदर्शन हिट। यह स्वीकार्य हो सकता है, लेकिन एक संपत्ति सेट करना एक ऐसी जगह नहीं है जहां मैं अपने आवेदन को कई चक्रों तक खर्च करना चाहूंगा।
ब्रूनो ब्रेंट

1
@BrunoBrant क्या आप सुनिश्चित हैं कि कोई प्रदर्शन हिट है? ब्लॉग पोस्ट के अनुसार प्रतिबिंब रनटाइम (यानी स्थिर प्रतिबिंब) के बजाय संकलन समय के दौरान होता है।
नथानिएल एल्किंस

6
मेरा मानना ​​है कि आपका पूरा OnPropertyChanged <T> C # 6 के nameof ऑपरेटर के साथ अप्रचलित है, जिससे यह राक्षस थोड़ा चिकना हो जाता है।
ट्रुबेनफुच

5
@Traubenfuchs, वास्तव में, C # 5 की CallerMemberName विशेषता इसे और भी सरल बनाती है, क्योंकि आपको कुछ भी पास करने की आवश्यकता नहीं है ...
थॉमस लेवेस्क

120

इसमें Fody भी है जिसके पास एक PropertyChanged ऐड-इन है, जो आपको यह लिखने देता है:

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

... और संकलन के समय में संपत्ति के नोटिफिकेशन बदल दिए जाते हैं।


7
मुझे लगता है कि यह वही है जो ओपी देख रहा था जब उन्होंने पूछा कि "क्या हम खुद अपने गुणों में 'अधिसूचित' जैसा कुछ लागू कर सकते हैं। क्या आपकी कक्षा में
INotifyPropertyChanged

3
यह वास्तव में एकमात्र सुंदर समाधान है, और यह @Cadbloke के अनुसार त्रुटिपूर्ण काम करता है। और मैं बुनकर के बारे में भी उलझन में था, लेकिन मैंने आईएल कोड की जाँच / पुनरावृत्ति की और यह एकदम सही है, यह सरल है, आपको सभी की आवश्यकता है और कोई नहीं। यह हुक या कॉल भी करता है जो भी विधि नाम आपने इसके लिए बेस क्लास में निर्दिष्ट किया है, चाहे NotifyOnProp ..., OnNotify ... कोई फर्क नहीं पड़ता, इसलिए किसी भी आधार वर्ग के साथ अच्छी तरह से काम करता है जो आपके पास हो सकता है और यह लागू करता है।
NSGaga-ज्यादातर-निष्क्रिय

1
आप आसानी से डबल-चेक कर सकते हैं कि बुनकर क्या कर रहा है, बिल्ड आउटपुट विंडो पर एक नज़र है, यह उन सभी संपत्तिवार चीजों को सूचीबद्ध करता है जो इसे बुना है। रेगेक्स पैटर्न के साथ VScolorOutput एक्सटेंशन का उपयोग करके "Fody/.*?:",LogCustom2,Trueइसे "कस्टम 2" रंग में हाइलाइट किया गया है। मैंने इसे चमकदार गुलाबी बनाया है ताकि इसे ढूंढना आसान हो। बस सब कुछ Fody, यह कुछ भी करने के लिए सबसे साफ तरीका है जिसमें बहुत दोहराव टाइपिंग है।
सीएडी ने

@mahmoudnezarsarhan नहीं, यह नहीं है, मुझे याद है कि इसे कॉन्फ़िगर करने के तरीके में थोड़ा बदलाव आया था, लेकिन फोडी प्रॉपर्टीचेंज अभी भी जीवित और सक्रिय है।
लैरी

65

मुझे लगता है कि लोगों को प्रदर्शन पर थोड़ा और ध्यान देना चाहिए; यह वास्तव में यूआई को प्रभावित करता है जब बहुत सी वस्तुओं को बाध्य किया जाता है (10,000+ पंक्तियों के साथ एक ग्रिड के बारे में सोचो), या यदि वस्तु का मूल्य बार-बार बदलता है (वास्तविक समय की निगरानी करने वाला ऐप)।

मैंने यहां और अन्य जगहों पर विभिन्न कार्यान्वयन किए और तुलना की; INOTifyPropertyChanged कार्यान्वयन की पूर्णता तुलना की जाँच करें ।


यहाँ परिणाम पर एक नज़र है अप्रधान बनाम रनटाइम


14
-1: कोई प्रदर्शन ओवरहेड नहीं है: CallerMemberName को संकलन समय पर शाब्दिक मूल्यों में बदल दिया जाता है। बस कोशिश करें और अपने एप्लिकेशन को अपघटित करें।
JYL

यहाँ प्रश्न और उत्तर के अनुसार है: stackoverflow.com/questions/22580623/…
uli78

1
@JYL, आप सही हैं कि CallerMemberName ने एक बड़ा ओवरहेड नहीं जोड़ा। पिछली बार जब मैंने कोशिश की थी तो मैंने कुछ गलत किया होगा। मैं ब्लॉग को अपडेट करूंगा और बाद में CallerMemberName और Fody कार्यान्वयन के लिए बेंचमार्क को प्रतिबिंबित करने के लिए उत्तर दूंगा।
पीजेन

1
यदि आपके पास UI में 10,000+ का ग्रिड है, तो आपको संभवतः प्रदर्शन को संभालने के लिए दृष्टिकोणों को संयोजित करना चाहिए, जैसे पेजिंग जहां आप केवल प्रति पृष्ठ 10, 50, 100, 250 हिट दिखाते हैं ...
Austin Rhymer

Austin Rhymer, यदि आपके पास लार्ग डेटा + 50 उपयोग डेटा वर्चुअलाइजेशन है तो सभी डेटा को लोड करने की कोई आवश्यकता नहीं है यह केवल उस डेटा को लोड करेगा जो वर्तमान स्कोलिंग प्रदर्शित क्षेत्र पर दिखाई देता है!
बिलाल

38

मैं http://timoch.com/blog/2013/08/annoyed-with-inotifypropertychange/ पर अपने ब्लॉग में एक Bindable वर्ग का परिचय देता हूँ Bindable एक प्रॉपर्टी बैग के रूप में एक शब्दकोश का उपयोग करता है। रेफरी मापदंडों का उपयोग करके अपने स्वयं के बैकिंग क्षेत्र का प्रबंधन करने के लिए एक उपवर्ग के लिए आवश्यक ओवरलोड को जोड़ना काफी आसान है।

  • कोई जादुई तार नहीं
  • कोई प्रतिबिंब नहीं
  • डिफ़ॉल्ट डिक्शनरी लुकअप को दबाने के लिए सुधार किया जा सकता है

कोड:

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

इसका उपयोग इस तरह किया जा सकता है:

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

2
यह एक अच्छा समाधान है, लेकिन केवल नकारात्मक पक्ष यह है कि मुक्केबाजी / अनबॉक्सिंग को शामिल करने वाला एक छोटा प्रदर्शन है।
21

1
मैं उपयोग करने का सुझाव देना चाहूंगा protected T Get<T>(T defaultValue, [CallerMemberName] string name = null)और if (_properties.ContainsKey(name) && Equals(value, Get<T>(default(T), name)))सेट में भी जांच करूंगा (जब पहली बार डिफ़ॉल्ट मान पर सेट करने और सहेजने के लिए)
Miquel

1
@ कस्टम डिफ़ॉल्ट मानों के लिए समर्थन जोड़ना सुनिश्चित करने के लिए उपयोगी हो सकता है, हालांकि आपको केवल तब बदले जाने वाली घटना को बढ़ाने के लिए सावधान रहना चाहिए जब मूल्य वास्तव में बदल जाता है। संपत्ति को उसी मूल्य पर सेट करना, जिसमें घटनाओं को नहीं उठाना चाहिए था। मुझे उन अधिकांश मामलों में स्वीकार करना चाहिए जो हानिरहित हैं, लेकिन यूआई जवाबदेही को नष्ट करने वाली घटनाओं के साथ हजारों बार समान मूल्य के गुणों के सेट होने के कारण मैं कुछ समय से काफी कम है।
टिआमोच

1
@stakx मेरे पास कुछ ऐसे अनुप्रयोग हैं जो पूर्ववत / रिड्यू के लिए यादगार पैटर्न का समर्थन करने या उन अनुप्रयोगों में कार्य पैटर्न की इकाई को सक्षम करने के लिए हैं जहां nhibernate प्रयोग करने योग्य नहीं है
TiMoch

1
मैं वास्तव में इस विशेष समाधान को पसंद करता हूं: लघु संकेतन, कोई डायनेमिक प्रॉक्सी सामान, कोई आईएल-मेडलिंग, आदि। हालांकि, आप हर बार टी को निर्दिष्ट करने की आवश्यकता को हटाकर इसे कम कर सकते हैं । मुझे पता है, यह रनटाइम प्रदर्शन को प्रभावित करता है, लेकिन अब गेटर्स और सेटर के लिए कोड अंत में हमेशा एक ही हो सकता है और एक लाइन में , प्रभु की प्रशंसा करें! PS आपको अपने गेट विधि के अंदर अतिरिक्त ध्यान रखना चाहिए (एक बार जब आप बेस क्लास लिखते हैं) जब वैल्यूएट के लिए डिफ़ॉल्ट मानों को डायनामिक के रूप में वापस करते हैं। हमेशा सही डिफ़ॉल्ट मान
लौटाएँ

15

मुझे वास्तव में अभी तक यह कोशिश करने का मौका नहीं मिला है, लेकिन अगली बार जब मैं INotifyPropertyChanged के लिए एक बड़ी आवश्यकता के साथ एक परियोजना स्थापित कर रहा हूं, तो मैं एक Postharp विशेषता लिखने का इरादा कर रहा हूं जो संकलन समय पर कोड को इंजेक्ट करेगा। कुछ इस तरह:

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

हो जाएगा:

private string _firstName;

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

मुझे यकीन नहीं है कि यह अभ्यास में काम करेगा और मुझे नीचे बैठने और इसे आज़माने की ज़रूरत है, लेकिन मैं नहीं देखता कि क्यों नहीं। मुझे उन स्थितियों के लिए कुछ मापदंडों को स्वीकार करने की आवश्यकता हो सकती है जहां एक से अधिक OnPropertyChanged को ट्रिगर करने की आवश्यकता है (यदि, उदाहरण के लिए, मेरे पास ऊपर की कक्षा में एक FullName संपत्ति थी)

वर्तमान में मैं Resharper में एक कस्टम टेम्प्लेट का उपयोग कर रहा हूं, लेकिन यहां तक ​​कि मैं अपने सभी गुणों से इतना लंबा होने के कारण तंग आ रहा हूं।


आह, एक त्वरित Google खोज (जो मुझे यह लिखने से पहले करनी चाहिए थी) से पता चलता है कि कम से कम एक व्यक्ति ने यहां से पहले ऐसा कुछ किया है । बिल्कुल नहीं जो मेरे मन में था, लेकिन यह दिखाने के लिए पर्याप्त था कि सिद्धांत अच्छा है।


6
फ़ॉडी नामक एक नि: शुल्क उपकरण वही काम करता है, जो सामान्य संकलन-समय कोड इंजेक्टर के रूप में कार्य करता है। यह Nuget में डाउनलोड करने योग्य है, जैसा कि इसके PropertyChanged और PropertyChanging प्लगइन पैकेज हैं।
त्रिनको

11

हां, बेहतर तरीका निश्चित रूप से मौजूद है। यह रहा:

कदम ट्यूटोरियल द्वारा कदम मेरे द्वारा सिकुड़ गया है, इस आधार पर उपयोगी लेख

  • नया प्रोजेक्ट बनाएं
  • परियोजना में महल कोर पैकेज स्थापित करें

इंस्टॉल-पैकेज कैसल.कोर

  • Mvvm लाइट लाइब्रेरी को ही स्थापित करें

स्थापित-पैकेज MvvmLightLibs

  • परियोजना में दो वर्ग जोड़ें:

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;
    }
}
  • उदाहरण के लिए, अपना दृश्य मॉडल बनाएं:

-

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

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

        public void TestAction()
        {
            Trace.WriteLine(MainTextBox);
        }
    }
  • बाँध को xaml में डालें:

    <TextBox Text="{Binding MainTextBox}" ></TextBox>
    <Button Command="{Binding TestActionCommand}" >Test</Button>
  • कोड के पीछे कोड की फाइल को फाइल में रखें MainWindow.xaml.cs इस तरह:

DataContext = ProxyCreator.MakeINotifyPropertyChanged<MainViewModel>();

  • का आनंद लें।

यहाँ छवि विवरण दर्ज करें

ध्यान!!! सभी बंधे हुए गुणों को कीवर्ड वर्चुअल से सजाया जाना चाहिए क्योंकि वे ओवरराइडिंग के लिए महल प्रॉक्सी द्वारा उपयोग किए जाते हैं।


मुझे यह जानने में दिलचस्पी है कि आप किस कैसल का उपयोग कर रहे हैं। मैं 3.3.0 का उपयोग कर रहा है और CreateClassProxy विधि उन पैरामीटर नहीं है: type, interfaces to apply, interceptors
21

कोई बात नहीं, मैं जेनेरिक CreateClassProxy<T>विधि का उपयोग कर रहा था । बहुत अलग ... हम्म, सोच रहा था कि जेनेरिक विधि के साथ इतना सीमित क्यों है। :(
आईब्रेट

7

एक बहुत ही एओपी जैसा दृष्टिकोण है कि फ्लाई पर पहले से ही तत्काल वस्तु पर INotifyPropertyChanged सामान इंजेक्ट करें। आप कैसल डायनामिक्सप्रोक्सी जैसी चीज के साथ ऐसा कर सकते हैं। यहाँ एक लेख है जो तकनीक की व्याख्या करता है:

किसी मौजूदा ऑब्जेक्ट में INotifyPropertyChanged जोड़ना


5

यहाँ देखें: http://dotnet-forum.de/blogs/thearchitect/archive/2012/11/01/die-optimale-implementierung-des-inotifypropertychanged-interfaces.aspx

यह जर्मन में लिखा गया है, लेकिन आप ViewModelBase.cs डाउनलोड कर सकते हैं। सीएस-फाइल में सभी टिप्पणियाँ अंग्रेजी में लिखी जाती हैं।

इस ViewModelBase-Class के साथ यह संभव है कि यह जानी-मानी डिपेंडेंसी प्रॉपर्टीज की तरह ही बाइंडेबल प्रॉपर्टीज़ को लागू करे:

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

1
लिंक टूट गया है।
गुज़रे जनाब

4

थॉमस द्वारा उत्तर के आधार पर जिसे मार्क द्वारा उत्तर के रूप में अनुकूलित किया गया था, मैंने प्रतिबिंबित की गई संपत्ति को आधार वर्ग में बदल दिया है:

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

उपयोग थॉमस के उत्तर के समान है सिवाय इसके कि आप अतिरिक्त संपत्तियों को पास कर सकें। यह गणना किए गए स्तंभों को संभालने के लिए आवश्यक था जिन्हें ग्रिड में ताज़ा करने की आवश्यकता होती है।

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

मेरे पास यह एक DataGridView के माध्यम से उजागर एक बाइंडिंगलिस्ट में संग्रहीत वस्तुओं का एक संग्रह है। इसने मुझे ग्रिड में मैनुअल रिफ्रेश () कॉल करने की आवश्यकता को समाप्त कर दिया है।


4

मुझे यप्पी नामक अपना दृष्टिकोण प्रस्तुत करना चाहिए । यह रनटाइम प्रॉक्सी से संबंधित है। व्युत्पन्न वर्ग जनरेटर, एक मौजूदा वस्तु या प्रकार में नई कार्यक्षमता जोड़ते हैं, जैसे कि जाति परियोजना का डायनेमिक प्रॉक्सी।

यह बेस क्लास में एक बार INotifyPropertyChanged को लागू करने की अनुमति देता है, और फिर निम्न शैली में व्युत्पन्न वर्गों की घोषणा करता है, फिर भी नई संपत्तियों के लिए INotifyPropertyChanged का समर्थन कर रहा है:

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

व्युत्पन्न वर्ग या प्रॉक्सी निर्माण की जटिलता को निम्नलिखित पंक्ति के पीछे छिपाया जा सकता है:

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

और सभी INotifyPropertyChanged कार्यान्वयन कार्य इस तरह किया जा सकता है:

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

यह रीफैक्टरिंग के लिए पूरी तरह से सुरक्षित है, प्रकार के निर्माण के बाद कोई प्रतिबिंब नहीं बनाता है और पर्याप्त तेजी से होता है।


आपको TDeclarationटाइप पैरामीटर की आवश्यकता क्यों है PropertyImplementation? निश्चित रूप से आप कॉल करने के लिए उपयुक्त प्रकार (कॉल्वर्ट नहीं) पा सकते हैं TImplementation?
एंड्रयू सविनाख

ज्यादातर मामलों में TImplementation काम करती है। अपवाद हैं: 1. "नए" सी # कीवर्ड के साथ पुन: परिभाषित गुण। 2. स्पष्ट इंटरफ़ेस कार्यान्वयन के गुण।
केलकुलिन

3

ये सभी उत्तर बहुत अच्छे हैं।

मेरा समाधान काम करने के लिए कोड स्निपेट्स का उपयोग कर रहा है।

यह PropertyChanged घटना के लिए सबसे सरल कॉल का उपयोग करता है।

इस स्निपेट को सहेजें और इसका उपयोग करें क्योंकि आप 'फुलप्रॉप' स्निपेट का उपयोग करते हैं।

दृश्य स्टूडियो में 'टूल \ कोड स्निपेट प्रबंधक ...' मेनू में स्थान पाया जा सकता है।

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

आप अपनी इच्छानुसार कॉल को संशोधित कर सकते हैं (उपरोक्त समाधानों का उपयोग करने के लिए)


2

यदि आप .NET 4.5 में डायनामिक्स का उपयोग कर रहे हैं तो आपको चिंता करने की आवश्यकता नहीं है INotifyPropertyChanged

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

यदि नाम कुछ नियंत्रण के लिए बाध्य है तो यह ठीक काम करता है।


1
इसका उपयोग करने का कोई नुकसान?
जूफो १५'१

2

एक अन्य संयुक्त समाधान 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));
        }
    }
}

उपयोग:

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

2
क्या यह उपवास है? क्या स्टैक फ्रेम तक पहुंच कुछ अनुमति की आवश्यकता से बाध्य नहीं है? क्या यह एसिंक्स / वेट का उपयोग करने के संदर्भ में मजबूत है?
स्टीफन गौरीचोन

@ स्टीफन गॉरिचोन नहीं, यह नहीं है। स्टैक फ्रेम तक पहुंचने का मतलब है कि ज्यादातर मामलों में काफी अच्छा प्रदर्शन हो।
ब्रूनो ब्रेंट

हाँ है, तो आप पर इसे देख सकते हैं codereview.stackexchange.com/questions/13823/...
Ofir

ध्यान दें कि इनलाइनिंग get_Fooरिलीज़ मोड में विधि छिपा सकती है।
bytecode77

2

मैंने पुन: उपयोग के लिए अपने बेस लाइब्रेरी में एक एक्सटेंशन विधि बनाई:

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 के कारण .Net 4.5 के साथ काम करता है । यदि आप इसे पहले .Net संस्करण के साथ उपयोग करना चाहते हैं, तो आपको विधि घोषणा को इसमें से बदलना होगा: ...,[CallerMemberName] string propertyName = "", ...से...,string propertyName, ...

उपयोग:

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

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

2

मैंने इस तरीके से हल किया (यह थोड़ा श्रमसाध्य है, लेकिन यह निश्चित रूप से रनटाइम में तेज है)।

VB में (क्षमा करें, लेकिन मुझे लगता है कि C # में इसका अनुवाद कठिन नहीं है), मैं RE के साथ इसका प्रतिस्थापन करता हूँ:

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

साथ में:

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

यह इस तरह से सभी कोड ट्रांसफ़ॉर्म:

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

में

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

और अगर मुझे अधिक पठनीय कोड चाहिए, तो मैं निम्नलिखित प्रतिस्थापन बनाने के विपरीत हो सकता हूं:

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

साथ में

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

मैं सेट विधि के आईएल कोड को बदलने के लिए फेंक देता हूं, लेकिन मैं आईएल में बहुत सारे संकलित कोड नहीं लिख सकता ... अगर एक दिन मैं इसे लिखता हूं, तो मैं आपको कहूंगा!


2

मैं इसे एक स्निपेट के रूप में चारों ओर रखता हूं। सी # 6 हैंडलर को लागू करने के लिए कुछ अच्छा वाक्यविन्यास जोड़ता है।

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

यहाँ NotityPropertyChanged का एक Unity3D या नॉन-कॉलरमेम्बरनाम संस्करण है

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
}

यह कोड आपको इस तरह से संपत्ति समर्थन फ़ील्ड लिखने में सक्षम बनाता है:

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

इसके अलावा, अगर आप एक पैटर्न / खोज स्निपेट बनाते हैं, तो आप ऊपर के बैकिंग में सरल प्रोप फील्ड को परिवर्तित करके वर्कफ़्लो कर सकते हैं।

खोज पैटर्न:

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

पैटर्न बदलें:

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

2

मैंने एक लेख लिखा है जो इसके साथ मदद करता है ( https://msdn.microsoft.com/magazine/mt736453 )। आप SolSoft.DataBinding NuGet पैकेज का उपयोग कर सकते हैं। फिर आप इस तरह कोड लिख सकते हैं:

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

लाभ:

  1. आधार वर्ग वैकल्पिक है
  2. हर 'सेट वैल्यू' पर कोई प्रतिबिंब नहीं
  3. ऐसे गुण हो सकते हैं जो अन्य गुणों पर निर्भर करते हैं, और वे सभी स्वचालित रूप से उचित घटनाओं को बढ़ाते हैं (लेख में इसका एक उदाहरण है)

2

जबकि जाहिर तौर पर ऐसा करने के बहुत सारे तरीके हैं, एओपी जादू के उत्तर के अपवाद के साथ, कोई भी जवाब नहीं दिखता है कि मॉडल के संपत्ति को सीधे देखने के मॉडल से संदर्भ के लिए एक स्थानीय फ़ील्ड के बिना सेट किया गया है।

मुद्दा यह है कि आप किसी संपत्ति का संदर्भ नहीं दे सकते। हालाँकि, आप उस प्रॉपर्टी को सेट करने के लिए एक्शन का उपयोग कर सकते हैं।

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

यह निम्नलिखित कोड निकालने की तरह इस्तेमाल किया जा सकता है।

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

विधि के पूर्ण कार्यान्वयन के लिए इस BitBucket रेपो को देखें और एक ही परिणाम को प्राप्त करने के कुछ अलग तरीके, जिसमें LINQ और एक विधि जो प्रतिबिंब का उपयोग करता है शामिल है। ध्यान दें कि ये विधियां धीमी प्रदर्शन वार हैं।


1

इन प्रकार की संपत्तियों को लागू करते समय आप जिन अन्य चीजों पर विचार करना चाहते हैं, वह यह तथ्य है कि इनोटिफ़प्रोपरेटीचांग * एड * इन इवेंट इवेंट क्लास दोनों का उपयोग करता है।

यदि आपके पास बड़ी संख्या में संपत्तियां हैं जो सेट की जा रही हैं, तो घटना तर्क श्रेणी उदाहरणों की संख्या बहुत बड़ी हो सकती है, आपको उन्हें कैशिंग पर विचार करना चाहिए क्योंकि वे उन क्षेत्रों में से एक हैं जो एक स्ट्रिंग विस्फोट हो सकते हैं।

इस कार्यान्वयन और स्पष्टीकरण की ओर ध्यान दें कि इसकी कल्पना क्यों की गई थी।

जोश स्मिथ ब्लॉग


1

मैंने अभी-अभी ActiveSharp - Automatic INotifyPropertyChanged पाया है , मुझे अभी तक इसका उपयोग नहीं करना है, लेकिन यह अच्छा लग रहा है।

इसे वेब साइट से उद्धृत करने के लिए ...


एक स्ट्रिंग के रूप में संपत्ति का नाम निर्दिष्ट किए बिना संपत्ति परिवर्तन सूचनाएं भेजें।

इसके बजाय, इस तरह से गुण लिखें:

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

ध्यान दें कि संपत्ति का नाम स्ट्रिंग के रूप में शामिल करने की कोई आवश्यकता नहीं है। ActiveSharp मज़बूती से और सही ढंग से आंकड़े जो खुद के लिए बाहर। यह इस तथ्य के आधार पर काम करता है कि आपकी संपत्ति कार्यान्वयन रेफरी द्वारा बैकिंग फ़ील्ड (_foo) से गुजरती है। (ActiveSharp उस "रीफ" कॉल का उपयोग करता है ताकि यह पता लगाया जा सके कि कौन सा बैकिंग फ़ील्ड पारित किया गया था, और किस क्षेत्र से यह संपत्ति की पहचान करता है)।


1

प्रतिबिंब का उपयोग करते हुए एक विचार:

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

यह बहुत अच्छा है, मुझे यह अभिव्यक्ति दृष्टिकोण से अधिक पसंद है। नकारात्मक पक्ष पर, धीमा होना चाहिए।
नवाफाल

1

मुझे लगता है कि इस सवाल का पहले से ही एक गजियन उत्तर है, लेकिन उनमें से कोई भी मेरे लिए बिल्कुल सही नहीं लगा। मेरा मुद्दा यह है कि मैं किसी भी प्रदर्शन को हिट नहीं करना चाहता हूं और अकेले उस कारण के लिए थोड़ी सी शालीनता के साथ तैयार हूं। मैं या तो ऑटो संपत्तियों के लिए बहुत ज्यादा परवाह नहीं करता, जिसने मुझे निम्नलिखित समाधान के लिए प्रेरित किया:

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

दूसरे शब्दों में, उपरोक्त समाधान सुविधाजनक है यदि आपको ऐसा करने में कोई आपत्ति नहीं है:

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

पेशेवरों

  • कोई प्रतिबिंब नहीं
  • केवल पुराना मान होने पर सूचित करता है! = नया मान
  • एक साथ कई गुण सूचित करें

विपक्ष

  • कोई भी ऑटो गुण (आप दोनों के लिए समर्थन जोड़ सकते हैं, हालांकि!)
  • कुछ वाचालता
  • बॉक्सिंग (छोटा प्रदर्शन हिट?)

काश, ऐसा करने से यह अभी भी बेहतर है,

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

हर एक संपत्ति के लिए, जो अतिरिक्त क्रियाशीलता के साथ एक बुरा सपना बन जाती है; --(

ध्यान दें, मैं यह दावा नहीं करता कि यह समाधान दूसरों की तुलना में बेहतर प्रदर्शन-वार है, बस यह उन लोगों के लिए एक व्यवहार्य समाधान है जो प्रस्तुत किए गए अन्य समाधानों को पसंद नहीं करते हैं।


1

मैं इस आधार वर्ग के साथ पालन करने योग्य पैटर्न को लागू करने के लिए आया था, बहुत ज्यादा आपको क्या ज़रूरत है ( "स्वचालित रूप से सेट को लागू करने और प्राप्त करने के लिए " )। मैंने प्रोटोटाइप के रूप में इस पर एक घंटे की लाइन बिताई, इसलिए इसमें कई यूनिट परीक्षण नहीं हैं, लेकिन अवधारणा साबित होती है। ध्यान दें कि यह Dictionary<string, ObservablePropertyContext>निजी क्षेत्रों की आवश्यकता को दूर करने के लिए उपयोग करता है ।

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

यहाँ उपयोग है

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 का उपयोग करने का सुझाव देता हूं। यह फोडी को छोड़कर सबसे छोटी विधि है।

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

बजाय

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

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

( DOCS )


0

एक अन्य विचार...

 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

=> यहाँ निम्नलिखित विशेषताओं के साथ मेरा समाधान है

 public ResourceStatus Status
 {
     get { return _status; }
     set
     {
         _status = value;
         Notify(Npcea.Status,Npcea.Comments);
     }
 }
  1. कोई पुनर्वित्त नहीं
  2. लघु संकेतन
  3. आपके व्यवसाय कोड में कोई जादुई स्ट्रिंग नहीं है
  4. आवेदन भर में PropertyChangedEventArgs की पुन: प्रयोज्यता
  5. एक बयान में कई संपत्तियों को सूचित करने की संभावना

0

इसे इस्तेमाल करो

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

}

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.