Насколько это медленно? INotifyPropertyChanged с использованием StackTrace - PullRequest
5 голосов
/ 23 декабря 2010

Сегодня я столкнулся с интересным способом реализации интерфейса INotifyPropertyChanged. Вместо передачи строкового имени измененного свойства или лямбда-выражения мы можем просто вызвать RaisePropertyChanged (); отметьте, что вызов не имеет параметров. Это код в методе RaisePropertyChanged ():

public virtual void RaisePropertyChanged()
{
    var frames = new System.Diagnostics.StackTrace();
    for (var i = 0; i < frames.FrameCount; i++)
    {
        var frame = frames.GetFrame(i).GetMethod() as MethodInfo;
        if (frame != null)
            if (frame.IsSpecialName && frame.Name.StartsWith("set_"))
            {
                RaisePropertyChanged(frame.Name.Substring(4));
                return;
            }
    }
    throw new InvalidOperationException("NotifyPropertyChanged() can only by invoked within a property setter.");
}

И это свойство, которое будет уведомлять своих иждивенцев о своем изменении:

public string MyProperty
{
    get { return _myField; }
    set
    {
        _myField= value;
        RaisePropertyChanged();
    }
}

Хотя я нахожу этот подход интересным, я думаю, что снижение производительности может быть серьезным в случае частого изменения свойства ... или если каждое свойство в нашем приложении использует этот подход для уведомления о своем изменении.

Я бы хотел услышать ваше мнение. (больше нет флажка сообщества-вики?) Будет ли этот подход очень неэффективным?

Источник: Статья, в которой представлен этот подход

Ответы [ 5 ]

8 голосов
/ 23 декабря 2010

Я только что проверил, используя этот код .(Обратите внимание, что я обошел ограничение , указанное в ответе Вим Коенена , используя атрибут MethodImpl . У меня есть сомнения относительно того, является ли это верным обходным решением.)

Результаты:

Raised event using reflection 1000 times in 25.5334 ms.
Raised event WITHOUT reflection 1000 times in 0.01 ms.

Итак, вы видите, что решение, включающее трассировку стека, в примерно в 2500 раз дороже"нормального" решения.

Это пропорциональноответь в любом случае.Лично мне не нравится эта идея (хотя и умная) по причинам, выходящим за рамки только производительности;но, очевидно, это ваш звонок.


Редактировать : Для записи я был вынужден написать сообщение в блоге об этом - в частности, отот факт, что некоторые разработчики будут испытывать искушение использовать подобный подход, несмотря на снижение производительности.

Согласны ли вы с моими чувствами по этому вопросу или нет (я понимаю, что снижение производительности незначительно в абсолютные термины), я чувствую, что реальный смертельный удар по этой идее заключается в том, что для того, чтобы она была даже удаленно устойчивой, необходимо украсить каждый установщик свойств, из которого вы намереваетесь вызывать RaisePropertyChanged с атрибутом MethodImpl, пропускающим MethodImplOptions.NoInlining ..., что прямо здесь сводит на нет все те сбережения при печати, которые вы получили в противном случае.

Таким образом, вы остаетесь с чистыми потерями во времени разработки (однакомного секунд потребовалось, чтобы вы набрали всю MethodImpl часть атрибута), плюс штраф за производительность.Очень мало причин идти по этому пути, если вы спросите меня.

2 голосов
/ 23 декабря 2010

Да, кажется, это большая работа, она будет медленной, и вы рискуете получить метод inline.

Если вы хотите сделать это, я бы предложил поставить [MethodImplAttribute], который говорит, что не встроен.

Мое предложение заключается в том, чтобы вместо этого использовать DynamicProxy, поскольку он будет намного проще и намного быстрее, чем этот подход, единственным недостатком является то, что вы должны указать метод как виртуальный. Например, в моем прокси в процессе работы я указываю определение метапрограммирования и привязываю его к своим свойствам, вот мой перехватчик NotifyPropertyChanged.

    private static void SetterInterceptor<T, TProperty>(ProxiedProperty<T, TProperty> property, T target, TProperty value) where T:class,IAutoNotifyPropertyChanged
    {
        TProperty oldValue;
        if (!EqualityComparer<TProperty>.Default.Equals(oldValue = property.GetMethod.CallBaseDelegate(target), value))
        {
            property.SetMethod.CallBaseDelegate(target, value);
            target.OnPropertyChanged(new PropertyChangedEventArgs(property.Property.Name));
        }
    }

Тогда это просто цикл foreach по интересующим меня свойствам с вызовами Delegate.CreateDelegate для выполнения привязки.

0 голосов
/ 23 декабря 2010

Другой подход, который мне нравится больше, здесь:

Вы можете получить название объекта, используя отражение лямбда-функции, которая звонит получателю собственности.

Как вызвать событие PropertyChanged без использования имени строки

0 голосов
/ 23 декабря 2010

Производительность не имеет значения, потому что в связанной статье говорится, что она на самом деле не работает:

Видимо, это метод, который был опробован в прошлом.Как отметил Сергей Барский, JITter, скорее всего, встроит метод и сломает кадр стека.

0 голосов
/ 23 декабря 2010

У меня нет четких доказательств, но я верю, что да, это будет очень дорого, особенно если это сделает каждая собственность.

...