Есть ли способ вызвать метод в наборе свойств auto? [Дубликат]

Обновлено 2018 для Bootstrap 4.0.0

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

Демо: http://www.codeply.com/go/PgxKT3h6x6

.vert .carousel-item-next.carousel-item-left,
.vert .carousel-item-prev.carousel-item-right {
    -webkit-transform: translate3d(0, 0, 0);
            transform: translate3d(0, 0, 0);
}

.vert .carousel-item-next,
.vert .active.carousel-item-right {
    -webkit-transform: translate3d(0, 100%, 0);
            transform: translate3d(0, 100% 0);
}

.vert .carousel-item-prev,
.vert .active.carousel-item-left {
-webkit-transform: translate3d(0,-100%, 0);
        transform: translate3d(0,-100%, 0);
}

Вертикальная карусельная демонстрация

572
задан 3 revs, 3 users 75% 9 April 2013 в 14:04
поделиться

29 ответов

Не используя что-то вроде postsharp, минимальная версия, которую я использую, использует что-то вроде:

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:

private string name;
public string Name
{
    get => name;
    set => SetField(ref name, value);
}
515
ответ дан 10 revs, 8 users 74% 22 August 2018 в 04:10
поделиться
  • 1
    Хороший трюк Марк! Я предложил улучшить использование выражения лямбда вместо имени свойства, см. Мой ответ – Thomas Levesque 22 August 2009 в 18:50
  • 2
    @Thomas - лямбда - все хорошо и хорошо, но это добавляет много накладных расходов для чего-то, что на самом деле очень просто. Удобный трюк, но я не уверен, что это всегда практично. – Marc Gravell♦ 22 August 2009 в 20:38
  • 3
    @Marc - Да, возможно, это может ухудшить производительность ... Однако мне очень нравится тот факт, что он проверяется во время компиляции и правильно реорганизуется с помощью «Переименовать». команда – Thomas Levesque 22 August 2009 в 22:23
  • 4
    @Gusdor, к счастью, с C # 5 нет необходимости идти на компромисс - вы можете получить лучшее из обоих через (как примечания Pedro77) [CallerMemberName] – Marc Gravell♦ 22 October 2013 в 09:30
  • 5
    @Gusdor язык и рамки являются отдельными; вы можете использовать компилятор C # 5, целевой .NET 4 и просто добавить отсутствующий атрибут самостоятельно - он будет работать нормально. Он просто должен иметь правильное имя и находиться в правильном пространстве имен. Он не должен быть в конкретной сборке. – Marc Gravell♦ 22 October 2013 в 09:39

=> здесь мое решение со следующими признаками

 public ResourceStatus Status
 {
     get { return _status; }
     set
     {
         _status = value;
         Notify(Npcea.Status,Npcea.Comments);
     }
 }
  1. no refelction
  2. короткая нотация
  3. нет волшебной строки в вашем бизнес-коде
  4. Повторное использование PropertyChangedEventArgs через приложение
  5. Возможность уведомлять несколько свойств в одном из операторов
31
ответ дан 2 revs 22 August 2018 в 04:10
поделиться
  • 1
    Бесплатный инструмент под названием «Фоди», похоже, делает то же самое, функционируя как генератор кода времени компиляции. Он загружается в Nuget, как и его плагины PropertyChanged и PropertyChanging. – Triynko 10 February 2014 в 22:39
  • 2
    Мне это и вправду нравится! Я имел ту же идею с Словарем и начал программировать. Я хотел опубликовать это и прокрутил вниз и увидел ваш ответ! Немного лучше, чем у меня. Ницца! :) – Felix Keil 17 September 2014 в 12:45
  • 3
    Это приятное решение, но единственным недостатком является то, что есть небольшой успех с участием бокса / распаковки. – MCattle 17 October 2014 в 22:57
  • 4
    Я бы предложил использовать protected T Get<T>(T defaultValue, [CallerMemberName] string name = null), а также проверить if (_properties.ContainsKey(name) && Equals(value, Get<T>(default(T), name))) в Set (для повышения и сохранения при первом задании значения по умолчанию) – Miquel 12 February 2015 в 03:35
  • 5
    @Miquel добавляет, что поддержка настраиваемых значений по умолчанию может быть полезна наверняка, однако вы должны быть осторожны, чтобы только повысить измененное событие, когда значение действительно изменилось. Установка свойства с тем же значением, которое у него было, не должно вызывать события. Должен признаться, что в большинстве случаев это безвредно, однако я несколько раз бился, когда свойства устанавливались в тысячи раз для того же значения с событиями, разрушающими отзывчивость пользовательского интерфейса. – TiMoch 1 March 2015 в 21:14
  • 6
    @stakx У меня есть несколько приложений, которые основаны на этом, чтобы поддержать шаблон памяти для отмены / повтора или включить единицу шаблона работы в приложениях, где nhibernate не используется – TiMoch 1 March 2015 в 21:16

Посмотрите здесь: http://dotnet-forum.de/blogs/thearchitect/archive/2012/11/01/die-optimale-implementierung-des-inotifypropertychanged-interfaces.aspx

Это написано на немецком языке, но вы можете загрузить ViewModelBase.cs. Все комментарии в cs-файле написаны на английском языке.

С помощью этого ViewModelBase-Class можно реализовать свойства связывания, аналогичные хорошо известным свойствам зависимостей:

public string SomeProperty
{
    get { return GetValue( () => SomeProperty ); }
    set { SetValue( () => SomeProperty, value ); }
}
5
ответ дан 2 revs, 2 users 77% 22 August 2018 в 04:10
поделиться

Очень похожий на AOP подход - это вводить материал INotifyPropertyChanged на уже созданный объект на лету. Вы можете сделать это с помощью чего-то вроде Castle DynamicProxy. Вот статья, которая объясняет технику:

Добавление INotifyPropertyChanged к существующему объекту

6
ответ дан 2 revs, 2 users 91% 22 August 2018 в 04:10
поделиться

Да, лучший способ, безусловно, существует. Вот он:

Пошаговое руководство сократилось мной на основе этой полезной статьи .

  • Создайте новый проект
  • Установить пакет ядра замка в проект

Install-Package Castle.Core

  • Установить только библиотеки mvvm light

Install-Package MvvmLightLibs

  • Добавить два класса в проект:

NotifierInterceptor g15]

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

  • Наслаждайтесь.

enter image description here [/g2]

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

9
ответ дан 2 revs, 2 users 99% 22 August 2018 в 04:10
поделиться
  • 1
    Это именно то, что я искал. Благодаря!!! – IAbstract 13 April 2016 в 21:18
  • 2
    Мне интересно узнать, какую версию Castle вы используете. Я использую 3.3.0, а метод CreateClassProxy не имеет этих параметров: type, interfaces to apply, interceptors. – IAbstract 13 April 2016 в 21:56
  • 3
    Nevermind, я использовал общий метод CreateClassProxy<T>. Многое ... Хммм, интересно, почему так ограниченный общий метод. :( – IAbstract 13 April 2016 в 22:25

Я использую следующий метод расширения (используя C # 6.0), чтобы сделать реализацию INPC максимально простой:

public static bool ChangeProperty<T>(this PropertyChangedEventHandler propertyChanged, ref T field, T value, object sender,
    IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
{
    if (comparer == null)
        comparer = EqualityComparer<T>.Default;

    if (comparer.Equals(field, value))
    {
        return false;
    }
    else
    {
        field = value;
        propertyChanged?.Invoke(sender, new PropertyChangedEventArgs(propertyName));
        return true;
    }
}

Реализация INPC сводится к (вы можете либо реализовать это каждый раз, либо создать базовый класс):

public class INPCBaseClass: INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected bool changeProperty<T>(ref T field, T value,
        IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
    {
        return PropertyChanged.ChangeProperty(ref field, value, this, comparer, propertyName);
    }
}

Затем напишите ваши свойства следующим образом:

private string testProperty;
public string TestProperty
{
    get { return testProperty; }
    set { changeProperty(ref testProperty, value); }
}

ПРИМЕЧАНИЕ. Вы можете опустить объявление [CallerMemberName] в методе расширения, если хотите , но я хотел сохранить его гибким.

Если у вас есть свойства без поля поддержки, вы можете перегрузить changeProperty:

protected bool changeProperty<T>(T property, Action<T> set, T value,
    IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
{
    bool ret = changeProperty(ref property, value, comparer, propertyName);
    if (ret)
        set(property);
    return ret;
}

Пример использования:

public string MyTestProperty
{
    get { return base.TestProperty; }
    set { changeProperty(base.TestProperty, (x) => { base.TestProperty = x; }, value); }
}
0
ответ дан 3 revs 22 August 2018 в 04:10
поделиться

Я думаю, что люди должны уделять немного больше внимания производительности, это действительно влияет на пользовательский интерфейс, когда есть много объектов, которые нужно связывать (подумайте о сетке с 10 000 + строками) или если значение объекта изменяется часто (в реальном времени приложение мониторинга).

Я взял различную реализацию, найденную здесь и в другом месте, и сделал сравнение, проверил это сравнение производительности реализаций INotifyPropertyChanged .


Вот загляните в результат Implemenation vs Runtime [/g1]

61
ответ дан 4 revs 22 August 2018 в 04:10
поделиться
  • 1
    -1: нет накладных расходов на производительность: CallerMemberName во время компиляции меняются на литеральные значения. Просто попробуйте декомпилировать приложение. – JYL 17 March 2014 в 02:11
  • 2
    вот соответствующий вопрос и ответ: stackoverflow.com/questions/22580623/… – uli78 25 March 2014 в 15:47
  • 3
    @JYL, вы правы, что CallerMemberName не добавил больших накладных расходов. Я, должно быть, что-то сделал неправильно в прошлый раз, когда попробовал. Я обновлю блог и отвечу, чтобы позже отразить эталон для реализации CallerMemberName и Fody. – Peijen 22 April 2014 в 06:43
  • 4
    Лямбда самая медленная, Простая - самая быстрая. Выдающаяся работа. – Jeson Martajaya 16 October 2015 в 13:12
  • 5
    Если в пользовательском интерфейсе есть сетка в 10 000+, то вы, вероятно, должны комбинировать подходы к управлению производительностью, например пейджинг, где вы показываете только 10, 50, 100, 250 просмотров на странице ... – Austin Rhymer 19 July 2017 в 15:06

Начиная с .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

Здесь приведены примеры здесь и здесь

Также см. Информация о вызывающем абоненте (C # и Visual Basic)

189
ответ дан 4 revs, 3 users 92% 22 August 2018 в 04:10
поделиться
  • 1
    Самый превосходный. Кажется, это определенно способ двигаться вперед. – HolySamosa 24 August 2012 в 17:35
  • 2
    Brilliant! Но почему это родовое? – abatishchev 12 December 2012 в 20:40
  • 3
    @abatishchev Я предполагаю, что это не обязательно, я просто играл с идеей о том, что функция также задала свойство. Я посмотрю, смогу ли я обновить свой ответ, предоставив полное решение. Дополнительные примеры делают хорошую работу, которая тем временем. – Daniel Little 13 December 2012 в 02:06
  • 4
    Он был введен C # 5.0. Это не имеет ничего общего с .net 4.5, но это отличное решение! – J. Lennon 18 February 2013 в 01:20
  • 5
    @J. Lennon .net 4.5 все еще имеет к этому какое-то отношение, ведь атрибут приходит откуда-то msdn.microsoft.com/en-au/library/… – Daniel Little 18 February 2013 в 01:43

Мне очень нравится решение Marc, но я думаю, что его можно немного улучшить, чтобы избежать использования «волшебной строки» (которая не поддерживает рефакторинг). Вместо использования имени свойства в виде строки легко сделать это лямбда-выражением:

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

Просто добавьте следующие методы в код Marc, он выполнит трюк:

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

160
ответ дан 5 revs, 3 users 78% 22 August 2018 в 04:10
поделиться
  • 1
    Этот метод использует, по крайней мере, одну структуру, ReactiveUI . – AlSki 11 October 2011 в 17:19
  • 2
    Очень поздно, это означало, что через отражение, что означало удар производительности. Это может быть приемлемо, но установка свойства не является местом, где я бы хотел, чтобы мое приложение проводило много циклов. – Bruno Brant 26 February 2015 в 20:46
  • 3
    @BrunoBrant Вы уверены, что есть хит производительности? Согласно сообщению в блоге, отражение происходит во время компиляции, а не времени выполнения (т. Е. Статического отражения). – Nathaniel Elkins 17 March 2015 в 22:47
  • 4
    Я считаю, что весь ваш OnPropertyChanged & lt; T & gt; устарел с именем оператора C # 6, сделав этого монстра немного более гладким. – Traubenfuchs 1 May 2015 в 18:04
  • 5
    @Traubenfuchs, на самом деле, атрибут CallerMemberName C # 5 делает его еще проще, поскольку вам вообще ничего не нужно передавать ... – Thomas Levesque 2 May 2015 в 00:02

Позвольте мне представить свой подход под названием Yappi . Он относится к генераторам производного класса Runtime proxy, добавляя новые функции к существующему объекту или типу, например Dynamic Proxy от Caste Project.

Он позволяет реализовать 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);
            };
        }
    }
}

Он полностью безопасен для рефакторинга, не использует отражения после построения типа и достаточно быстро.

4
ответ дан 8 revs, 2 users 99% 22 August 2018 в 04:10
поделиться
  • 1
    Зачем вам нужен параметр типа TDeclaration на PropertyImplementation? Разумеется, вы можете найти подходящий тип для вызова (не callvirt) получателя / сеттера с помощью только TImplementation? – Andrew Savinykh 22 June 2015 в 03:13
  • 2
    В большинстве случаев TImplementation работает. Исключениями являются: 1. Свойства, переопределенные с помощью "новых" C # keyvord. 2. Свойства реализации явного интерфейса. – Kelqualyn 29 July 2015 в 05:53

Если вы используете динамику в .NET 4.5, вам не нужно беспокоиться о INotifyPropertyChanged.

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

, если имя связано с каким-то контролем, оно просто отлично работает.

2
ответ дан Dilshod 22 August 2018 в 04:10
поделиться

Используйте это

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

}

0
ответ дан Dude505 22 August 2018 в 04:10
поделиться

Я создал метод расширения в моей базовой библиотеке для повторного использования:

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

Это работает с .Net 4.5 из-за CallerMemberNameAttribute . Если вы хотите использовать его с более ранней версией .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
ответ дан giammin 22 August 2018 в 04:10
поделиться
  • 1
    Ницца. Это экономит много дублирующихся кодов. +1 – real_yggdrasil 28 November 2017 в 11:34

Я придумал этот базовый класс для реализации наблюдаемого шаблона, в значительной степени делает то, что вам нужно ( «автоматически» , реализующий набор и get). Я потратил на это час в качестве прототипа, поэтому он не имеет много модульных тестов, но доказывает концепцию. Обратите внимание, что для удаления необходимости в частных полях используется 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
ответ дан Homero Barbosa 22 August 2018 в 04:10
поделиться

Я только что нашел ActiveSharp - Automatic INotifyPropertyChanged , я еще не использовал его, но он выглядит хорошо.

Чтобы процитировать его на веб-сайте ...


Отправить уведомления об изменении свойств без указания имени свойства как строки.

Вместо этого напишите свойства следующим образом:

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

Обратите внимание, что нет необходимости включать имя свойства в виде строки. ActiveSharp надежно и правильно определяет это для себя. Он работает на основе того факта, что ваша реализация свойства передает поле поддержки (_foo) по ссылке. (ActiveSharp использует этот вызов «ref» для определения того, какое поле поддержки было передано, и из поля оно идентифицирует свойство).

0
ответ дан Ian Ringrose 22 August 2018 в 04:10
поделиться

Идея с использованием отражения:

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
ответ дан Jack 22 August 2018 в 04:10
поделиться
  • 1
    Это довольно круто, мне нравится больше, чем подход к выражению. С другой стороны, это должно быть медленнее. – nawfal 12 August 2014 в 09:11

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

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

Плюсы

  • Отсутствие отражения
  • Уведомляет только, если старое значение! = новое значение
  • Уведомлять сразу несколько свойств

Минусы

  • Нет свойств авто (вы можете добавить поддержку для обоих, хотя!) [/ ​​g3]
  • Некоторая многословность
  • Бокс (небольшой удар производительности?)

Увы, все же лучше, чем делать это,

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

Для каждого отдельного имущества, которое становится кошмаром с Дополнительная информация: - (

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

1
ответ дан James M 22 August 2018 в 04:10
поделиться

Призма 5:

public abstract class BindableBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual bool SetProperty<T>(ref T storage,
                                          T value,
                                          [CallerMemberName] string propertyName = null)
    {
        if (object.Equals(storage, value)) return false;

        storage = value;
        this.OnPropertyChanged(propertyName);

        return true;
    }

    protected void OnPropertyChanged(string propertyName)
    {
        var eventHandler = this.PropertyChanged;
        if (eventHandler != null)
        {
            eventHandler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    protected void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
    {
        var propertyName = PropertySupport.ExtractPropertyName(propertyExpression);
        this.OnPropertyChanged(propertyName);
    }
}

public static class PropertySupport
{
    public static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
    {
        if (propertyExpression == null)
        {
            throw new ArgumentNullException("propertyExpression");
        }

        var memberExpression = propertyExpression.Body as MemberExpression;
        if (memberExpression == null)
        {
            throw new ArgumentException("The expression is not a member access expression.", "propertyExpression");
        }

        var property = memberExpression.Member as PropertyInfo;
        if (property == null)
        {
            throw new ArgumentException("The member access expression does not access a property.", "propertyExpression");
        }

        var getMethod = property.GetMethod;
        if (getMethod.IsStatic)
        {
            throw new ArgumentException("The referenced property is a static property.", "propertyExpression");
        }

        return memberExpression.Member.Name;
    }
}
0
ответ дан Jeson Martajaya 22 August 2018 в 04:10
поделиться

Я решил на этом пути (это немного работает, но это, безусловно, быстрее во время исполнения).

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

In

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}

я бросаю чтобы заменить IL-код установленного метода, но я не могу написать много скомпилированного кода в IL ... Если в день я его напишу, я скажу вам!

0
ответ дан Lucio Menci 22 August 2018 в 04:10
поделиться

Я написал статью, которая помогает с этим ( 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
ответ дан Mark Sowul 22 August 2018 в 04:10
поделиться

Я сохраняю это как фрагмент. C # 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
ответ дан Mike Ward 22 August 2018 в 04:10
поделиться

Другое комбинированное решение использует 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); }
    }
}
3
ответ дан Ofir 22 August 2018 в 04:10
поделиться
  • 1
    Это быстро? Не доступ к кадру стека связан с некоторыми требованиями к разрешению? Является ли это надежным в контексте использования async / wait? – Stéphane Gourichon 28 October 2014 в 20:30
  • 2
    @ StéphaneGourichon Нет, это не так. Доступ к фрейму стека означает значительное снижение производительности в большинстве случаев. – Bruno Brant 1 April 2015 в 13:02
  • 3
    Да, вы можете увидеть это в codereview.stackexchange.com/questions/13823/… – Ofir 27 June 2016 в 12:01

Другие вещи, которые вы, возможно, захотите рассмотреть при реализации этих свойств, это тот факт, что INotifyPropertyChang * ed * ing используют классы аргументов аргументов.

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

Взгляните на эту реализацию и объясните, почему это был задуман.

Блог Джоша Смита

1
ответ дан Peter 22 August 2018 в 04:10
поделиться

Вот версия Unity3D или non-CallerMemberName NotifyPropertyChanged

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

Кроме того, в resharper если вы создаете фрагмент шаблона / поиска, вы также можете автоматизировать свой рабочий процесс, преобразовывая простые прокси-поля в вышеупомянутую поддержку.

Поиск:

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

Заменить шаблон:

public $type$ $fname$
{
    get { return Get<$type$>(); }
    set { Set(value); }
}
2
ответ дан Scott Barnes 22 August 2018 в 04:10
поделиться

Также есть Fody , который имеет надстройку PropertyChanged , которая позволяет вам записать это:

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

... и при компиляции время вводит измененные изменения свойств.

102
ответ дан Tom Gilder 22 August 2018 в 04:10
поделиться
  • 1
    Я сразу переключился с решения принятого ответа на этот вопрос после некоторого google. – ender 30 June 2015 в 08:25
  • 2
    Простой и короткий, отличный, когда вы ленивы – HyunMi 25 May 2016 в 19:18
  • 3
    Я думаю, что это именно то, что искали OP, когда они спрашивали «Можем ли мы сами реализовать что-то вроде« уведомлять »в наших свойствах. Есть ли изящное решение для реализации INotifyPropertyChanged в вашем классе & quot; – Ashoat 5 August 2016 в 06:59
  • 4
    Это единственное изящное решение на самом деле, и оно работает безупречно, как сказал @CADbloke. И я тоже скептически относился к ткачу, но я проверил / перепроверял код IL позади, и это прекрасно, просто, все, что вам нужно, и больше ничего. Он также перехватывает и вызывает любое имя метода, которое вы назначили в базовом классе для него, независимо от того, NotifyOnProp ..., OnNotify ... не имеет значения, поэтому хорошо работает с любым базовым классом, который у вас есть, и который реализует INotify .. , – NSGaga 4 March 2017 в 23:04
  • 5
    Вы можете легко проверить, что делает ткач, посмотреть окно вывода сборки, в нем перечислены все свойства PropertyChanged, которые он соткан. Использование расширения VScolorOutput с шаблоном regex "Fody/.*?:",LogCustom2,True выделяет его в «Custom 2», цвет. Я сделал его ярко-розовым, поэтому его легко найти. Просто все, что угодно, это самый простой способ сделать что-нибудь, у которого много повторяющегося набора текста. – CAD bloke 25 July 2017 в 04:28

Другая идея ...

 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));
    }
}
31
ответ дан 2 revs 22 August 2018 в 04:10
поделиться
  • 1
    Бесплатный инструмент под названием «Фоди», похоже, делает то же самое, функционируя как генератор кода времени компиляции. Он загружается в Nuget, как и его плагины PropertyChanged и PropertyChanging. – Triynko 10 February 2014 в 22:39
  • 2
    Мне это и вправду нравится! Я имел ту же идею с Словарем и начал программировать. Я хотел опубликовать это и прокрутил вниз и увидел ваш ответ! Немного лучше, чем у меня. Ницца! :) – Felix Keil 17 September 2014 в 12:45
  • 3
    Это приятное решение, но единственным недостатком является то, что есть небольшой успех с участием бокса / распаковки. – MCattle 17 October 2014 в 22:57
  • 4
    Я бы предложил использовать protected T Get<T>(T defaultValue, [CallerMemberName] string name = null), а также проверить if (_properties.ContainsKey(name) && Equals(value, Get<T>(default(T), name))) в Set (для повышения и сохранения при первом задании значения по умолчанию) – Miquel 12 February 2015 в 03:35
  • 5
    @Miquel добавляет, что поддержка настраиваемых значений по умолчанию может быть полезна наверняка, однако вы должны быть осторожны, чтобы только повысить измененное событие, когда значение действительно изменилось. Установка свойства с тем же значением, которое у него было, не должно вызывать события. Должен признаться, что в большинстве случаев это безвредно, однако я несколько раз бился, когда свойства устанавливались в тысячи раз для того же значения с событиями, разрушающими отзывчивость пользовательского интерфейса. – TiMoch 1 March 2015 в 21:14
  • 6
    @stakx У меня есть несколько приложений, которые основаны на этом, чтобы поддержать шаблон памяти для отмены / повтора или включить единицу шаблона работы в приложениях, где nhibernate не используется – TiMoch 1 March 2015 в 21:16

Все эти ответы очень приятные.

Мое решение использует фрагменты кода для выполнения задания.

Это использует простейший вызов события PropertyChanged.

Сохраните этот фрагмент и используйте его, когда вы используете фрагмент «fullprop».

Местоположение можно найти в меню «Инструменты \ Код Snippet Manager ...» в 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>

Вы можете изменить вызов по своему усмотрению (использовать вышеупомянутые решения)

3
ответ дан Ofir 22 August 2018 в 04:10
поделиться
32
ответ дан 2 revs 5 November 2018 в 03:21
поделиться
32
ответ дан 2 revs 5 November 2018 в 03:21
поделиться
Другие вопросы по тегам:

Похожие вопросы: