اسم الخاصية INotifyPropertyChanged - الرمز الثابت مقابل الانعكاس؟

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

  •  02-07-2019
  •  | 
  •  

سؤال

ما هي أفضل طريقة لتحديد اسم الخاصية عند استخدام INotifyPropertyChanged؟

تقوم معظم الأمثلة بترميز اسم الخاصية كوسيطة في حدث PropertyChanged.كنت أفكر في استخدام MethodBase.GetCurrentMethod.Name.Substring(4) ولكنني أشعر بعدم الارتياح قليلاً بشأن الانعكاس الزائد.

هل كانت مفيدة؟

المحلول

لا تنسى شيئاً واحداً: PropertyChanged يتم استهلاك الحدث بشكل أساسي من خلال المكونات التي ستفعل ذلك استخدام الانعكاس للحصول على قيمة الخاصية المسماة.

المثال الأكثر وضوحا هو ربط البيانات.

عندما تطلق النار PropertyChanged الحدث، بتمرير اسم الخاصية كمعلمة، يجب أن تعرف ذلك من المرجح أن يستخدم المشترك في هذا الحدث الانعكاس عن طريق الاتصال مثلا GetProperty (على الأقل في المرة الأولى إذا كان يستخدم ذاكرة تخزين مؤقت لـ PropertyInfo)، ثم GetValue.هذا الاستدعاء الأخير عبارة عن استدعاء ديناميكي (MethodInfo.Invoc) لأسلوب getter للخاصية، والذي يكلف أكثر من GetProperty الذي يستعلم فقط عن بيانات التعريف.(لاحظ أن ربط البيانات يعتمد على الكل نوع واصف الشيء - ولكن التنفيذ الافتراضي يستخدم الانعكاس.)

لذلك، بالطبع، يعد استخدام أسماء خصائص التعليمات البرمجية الثابتة عند تشغيل PropertyChanged أكثر كفاءة من استخدام الانعكاس للحصول على اسم الخاصية ديناميكيًا، ولكن IMHO، من المهم موازنة أفكارك.في بعض الحالات، لا يكون عبء الأداء بالغ الأهمية، ويمكنك الاستفادة من آلية إطلاق الأحداث المكتوبة بقوة.

إليك ما أستخدمه أحيانًا في الإصدار C# 3.0، عندما لا يشكل الأداء مصدر قلق:

public class Person : INotifyPropertyChanged
{
    private string name;

    public string Name
    {
        get { return this.name; }
        set 
        { 
            this.name = value;
            FirePropertyChanged(p => p.Name);
        }
    }

    private void FirePropertyChanged<TValue>(Expression<Func<Person, TValue>> propertySelector)
    {
        if (PropertyChanged == null)
            return;

        var memberExpression = propertySelector.Body as MemberExpression;
        if (memberExpression == null)
            return;

        PropertyChanged(this, new PropertyChangedEventArgs(memberExpression.Member.Name));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

لاحظ استخدام شجرة التعبير للحصول على اسم الخاصية، واستخدام تعبير لامدا كتعبير Expression :

FirePropertyChanged(p => p.Name);

نصائح أخرى

في .NET 4.5 (C# 5.0) توجد سمة جديدة تسمى - CallerMemberName فهو يساعد على تجنب أسماء الخصائص المشفرة مما يمنع ظهور الأخطاء إذا قرر المطورون تغيير اسم الخاصية، إليك مثال:

public event PropertyChangedEventHandler PropertyChanged = delegate { };

public void OnPropertyChanged([CallerMemberName]string propertyName="")
{
    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

private string name;
public string Name
{
    get { return name; }
    set 
    { 
        name = value;
        OnPropertyChanged();
    }
}

إن الانعكاس الزائد هنا مبالغ فيه إلى حد كبير خاصة أنه يتم استدعاء INotifyPropertyChanged كثيراً.من الأفضل أن تقوم فقط بترميز القيمة إذا استطعت.

إذا لم تكن مهتمًا بالأداء، فسألقي نظرة على الطرق المختلفة المذكورة أدناه واختر ما يتطلب أقل قدر من الترميز.إذا كان بإمكانك فعل شيء ما لإزالة الحاجة إلى المكالمة الصريحة تمامًا، فسيكون ذلك هو الأفضل (على سبيل المثال:أوب).

يرجع نجاح الأداء المتضمن في استخدام أشجار التعبير إلى الدقة المتكررة لشجرة التعبير.

لا تزال التعليمة البرمجية التالية تستخدم أشجار التعبير، وبالتالي تتمتع بالمزايا المترتبة على كونها صديقة لإعادة البناء وصديقة للتشويش، ولكنها في الواقع أسرع بنسبة 40٪ تقريبًا (اختبارات تقريبية جدًا) من التقنية المعتادة - والتي تتكون من تجديد كائن PropertyChangedEventArgs لكل إشعار تغيير .

إنه أسرع ويتجنب نتيجة أداء شجرة التعبير لأننا نقوم بتخزين كائن PropertyChangedEventArgs ثابت لكل خاصية.

هناك شيء واحد لم أفعله بعد - أنوي إضافة بعض التعليمات البرمجية التي تتحقق من إنشاءات التصحيح بحيث يتطابق اسم الخاصية لكائن PropertChangedEventArgs المقدم مع الخاصية التي يتم استخدامه فيها - في الوقت الحالي مع هذا الرمز لا يزال الأمر كذلك من الممكن للمطور توفير الكائن الخطأ.

تحقق من ذلك:

    public class Observable<T> : INotifyPropertyChanged
    where T : Observable<T>
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected static PropertyChangedEventArgs CreateArgs(
        Expression<Func<T, object>> propertyExpression)
    {
        var lambda = propertyExpression as LambdaExpression;
        MemberExpression memberExpression;
        if (lambda.Body is UnaryExpression)
        {
            var unaryExpression = lambda.Body as UnaryExpression;
            memberExpression = unaryExpression.Operand as MemberExpression;
        }
        else
        {
            memberExpression = lambda.Body as MemberExpression;
        }

        var propertyInfo = memberExpression.Member as PropertyInfo;

        return new PropertyChangedEventArgs(propertyInfo.Name);
    }

    protected void NotifyChange(PropertyChangedEventArgs args)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, args);
        }
    }
}

public class Person : Observable<Person>
{
    // property change event arg objects
    static PropertyChangedEventArgs _firstNameChangeArgs = CreateArgs(x => x.FirstName);
    static PropertyChangedEventArgs _lastNameChangeArgs = CreateArgs(x => x.LastName);

    string _firstName;
    string _lastName;

    public string FirstName
    {
        get { return _firstName; }
        set
        {
            _firstName = value;
            NotifyChange(_firstNameChangeArgs);
        }
    }

    public string LastName
    {
        get { return _lastName; }
        set
        {
            _lastName = value;
            NotifyChange(_lastNameChangeArgs);
        }
    }
}

الرومانية:

أود أن أقول أنك لن تحتاج حتى إلى معلمة "الشخص" - وبالتالي، يجب أن يكون المقتطف العام تمامًا مثل المقتطف أدناه كافيًا:

private int age;
public int Age
{
  get { return age; }
  set
  {
    age = value;
    OnPropertyChanged(() => Age);
  }
}


private void OnPropertyChanged<T>(Expression<Func<T>> exp)
{
  //the cast will always succeed
  MemberExpression memberExpression = (MemberExpression) exp.Body;
  string propertyName = memberExpression.Member.Name;

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

...ومع ذلك، أفضّل الالتزام بمعلمات السلسلة مع التحقق الشرطي في إصدارات Debug.نشر جوش سميث عينة لطيفة على هذا:

فئة أساسية تقوم بتنفيذ INotifyPropertyChanged

هتاف :) فيليب

نعم، أرى استخدام الوظيفة التي تقترحها وبساطتها، ولكن عند النظر في تكلفة التشغيل بسبب التفكير، نعم هذه فكرة سيئة، ما أستخدمه في هذا السيناريو هو وجود مقتطف الكود تمت إضافتها بشكل صحيح للاستفادة من الوقت والخطأ في كتابة خاصية مع إطلاق حدث Notifyproperty.

طريقة أخرى لطيفة جدًا يمكنني التفكير فيها هي

التنفيذ التلقائي لـ INotifyPropertyChanged مع الجوانب
AOP:الجانب برمجة

مقالة جميلة عن مشروع الكود: تنفيذ AOP لـ INotifyPropertyChanged

قد تكون مهتمًا بهذه المناقشة حول

"أفضل الممارسات:كيفية تنفيذ INotifyPropertyChanged بشكل صحيح؟"

أيضاً.

دون أن أكون غير مهتم، بين Hardcode والانعكاس، خياري هو: notifypropertyweaver.

تسمح لك حزمة Visual Studio هذه بالحصول على فوائد الانعكاس (قابلية الصيانة، وسهولة القراءة،..) دون الحاجة إلى فقدان الأداء.

في الواقع، كل ما عليك فعله هو تنفيذ INotifyPropertyChanged وإضافة كافة "عناصر الإشعارات" إلى التجميع.

يعد هذا أيضًا قابلاً للقياس تمامًا إذا كنت تريد تحسين التعليمات البرمجية الخاصة بك بشكل كامل.

على سبيل المثال، باستخدام notifypropertyweaver، سيكون لديك هذا الكود في محررك:

public class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public string GivenNames { get; set; }
    public string FamilyName { get; set; }

    public string FullName
    {
        get
        {
            return string.Format("{0} {1}", GivenNames, FamilyName);
        }
    }
}

بدلاً من :

public class Person : INotifyPropertyChanged
{

    public event PropertyChangedEventHandler PropertyChanged;

    private string givenNames;
    public string GivenNames
    {
        get { return givenNames; }
        set
        {
            if (value != givenNames)
            {
                givenNames = value;
                OnPropertyChanged("GivenNames");
                OnPropertyChanged("FullName");
            }
        }
    }

    private string familyName;
    public string FamilyName
    {
        get { return familyName; }
        set
        {
            if (value != familyName)
            {
                familyName = value;
                OnPropertyChanged("FamilyName");
                OnPropertyChanged("FullName");
            }
        }
    }

    public string FullName
    {
        get
        {
            return string.Format("{0} {1}", GivenNames, FamilyName);
        }
    }

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

للناطقين بالفرنسية: تحسين صلاحية الكود الخاص بك وتبسيط حياتك مع notifypropertyweaver

منذ الإصدار C#6.0 يوجد اسم() سيتم تقييم الكلمة الأساسية في وقت الترجمة، لذلك سيكون لها الأداء كقيمة ثابتة ومحمية ضد عدم التطابق مع الخاصية التي تم الإخطار بها.

public event PropertyChangedEventHandler PropertyChanged;

protected void NotifyPropertyChanged(string info)
{       
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
}
public string SelectedItem
{
    get
    {
        return _selectedItem;
    }
    set
    {
        if (_selectedItem != value)
        {
            _selectedItem = value;
            NotifyPropertyChanged(nameof(SelectedItem));
        }
    }
}
private string _selectedItem;

بالإضافة إلى ذلك، وجدنا مشكلة حيث كان الحصول على اسم الأسلوب يعمل بشكل مختلف في Debug مقابل Debug.بنيات الإصدار:

http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/244d3f24-4cc4-4925-aebe-85f55b39ec92

(لم يكن الكود الذي كنا نستخدمه يعكس تمامًا الطريقة التي اقترحتها، ولكنه أقنعنا بأن تشفير اسم الخاصية هو الحل الأسرع والأكثر موثوقية.)

المشكلة في الطريقة القائمة على الانعكاس هي أنها مكلفة إلى حد ما، وليست سريعة جدًا.من المؤكد أنها أكثر مرونة وأقل هشاشة تجاه إعادة البناء.

ومع ذلك، يمكن أن يضر الأداء حقًا، خاصة عندما يتم استدعاء الأشياء بشكل متكرر.طريقة الإطار المكدس أيضًا (على ما أعتقد) بها مشكلات في CAS (على سبيل المثال.مستويات الثقة المقيدة، مثل XBAP).من الأفضل أن تقوم بترميزها بشكل ثابت.

إذا كنت تبحث عن إشعار سريع ومرن للملكية في WPF، فهناك حل - استخدم DependencyObject :) هذا ما تم تصميمه من أجله.إذا كنت لا تريد أن تأخذ التبعية، أو تقلق بشأن مشكلات تقارب مؤشر الترابط، فانقل اسم الخاصية إلى ثابت، وازدهر!لمصلحتك.

قد ترغب في تجنب INotifyPropertyChanged تمامًا.يضيف رمز مسك الدفاتر غير الضروري إلى مشروعك.فكر في استخدام تحديث عناصر التحكم .NET بدلاً من.

لقد فعلت شيئًا كهذا مرة واحدة كتجربة، وعملت بشكل جيد من الذاكرة، وأزلت الحاجة إلى ترميز جميع أسماء الخصائص في السلاسل.قد يكون الأداء محل مشكلة إذا كنت تقوم بإنشاء تطبيق خادم كبير الحجم، فمن المحتمل أنك لن تلاحظ الفرق أبدًا على سطح المكتب.

protected void OnPropertyChanged()
{
    OnPropertyChanged(PropertyName);
}

protected string PropertyName
{
    get
    {
        MethodBase mb = new StackFrame(1).GetMethod();
        string name = mb.Name;
        if(mb.Name.IndexOf("get_") > -1)
            name = mb.Name.Replace("get_", "");

        if(mb.Name.IndexOf("set_") > -1)
            name = mb.Name.Replace("set_", "");

        return name;
    }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top