Инъекция (функциональность INotifyPropertyChanged) в экземпляр класса - PullRequest
1 голос
/ 18 марта 2010

У меня есть класс, который реализует INotifyPropertyChanged. Я создаю экземпляр класса в некотором viewModel. Можно ли удалить эту функциональность из класса и внедрить ее после создания экземпляра? Я слышал, что ICustomTypeDescriptor сделает это возможным, но я не знаю, как его использовать.

public class C : ICustomNotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public int _id;
    public string _name;

    public int Id
    {
        get { return _id; }
        set
        {
            if (_id == value)
            {
                return;
            }

            _id = value;
            OnPropertyChanged("Id");
        }
    }

    public string Name
    {
        get { return _name; }
        set
        {
            if (_name == value)
            {
                return;
            }

            _name = value;
            OnPropertyChanged("Name");
        }
    }

    public void OnPropertyChanged(string name)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }

Ответы [ 2 ]

0 голосов
/ 18 марта 2010

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

Edit:

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

Обратите внимание, что я написал весь этот код в текстовом редакторе, а не в VisualStudio; это не было проверено в любом случае.

Добавить метод для включения уведомлений:

public class OptionalNotification : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    void OnPropertyChanged(string name) ...

    bool _shouldNotify;

    public void EnableNotifications()
    {
        _shouldNotify = true;
    }

    string _someProperty;
    public string SomeProperty
    {
        get { return _someProperty; }
        set 
        {
            if(_someProperty == value) return

            _someProperty = value;

            if(_shouldNotify) OnPropertyChanged("SomeProperty");
        }
    }
}

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

Другим вариантом будет использование шаблона «Фабрика», когда ваша Фабрика имеет внутренний доступ к логическому флагу и устанавливает его при создании.

Инкапсулировать условие в прокси:

public interface IEntity : INotifyPropertyChanged
{
    string SomeProperty { get; set; }
}

public class Entity : IEntity
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string name) ...

    string _someProperty;
    public string SomeProperty
    {
        get { return _someProperty; }
        set 
        {
            if(_someProperty == value) return

            _someProperty = value;
            OnPropertyChanged("SomeProperty");
        }
    }
}

public class EntityNotificationProxy : IEntity
{
    IEntity _inner;

    public EntityNotificationProxy(IEntity entity)
    {
        _inner = entity;
        _inner.PropertyChanged += (o,e) => { if(ShouldNotify) OnPropertyChanged(o,e); }
    }

    public bool ShouldNotify { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;

    void OnPropertyChanged(object sender, PropertChangedEventArgs e)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if(handler != null) handler(sender, e);
    }

    public string SomeProperty
    {
        get { return _inner.SomeProperty; }
        set 
        {
            if(_inner.SomeProperty == value) return

            _inner.SomeProperty = value;
        }
    }
}

Здесь ваши потребляющие классы получают прокси сущности вместо самой сущности (но не мудрее, потому что он ссылается только на IEntity, когда вы программируете интерфейсы / абстракции). Обертывание прокси может происходить на фабрике или через контейнер IoC / инфраструктуру DI.

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

Основным недостатком является то, что вам нужно создавать прокси для каждой реализации INotifyPropertyChanged, для которой вы хотите иметь это условное поведение.

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

public static class PropertyNotificationRegistry
{
    static IDictionary<INotifyPropertyChanged, bool> _registeredClasses
        = new Dictionary<INotifyPropertyChanged, bool>;

    static void Register(INotifyPropertyChanged o, bool shouldNotify)
    {
        if(!(_registeredClasses.ContainsKey(o)) _registeredClasses.Add(o, shouldNotify);
        // could also implement logic to update an existing class in the dictionary
    }

    public static void ShouldNotifyWhenPropertiesChange(this INotifyPropertyChanged o)
    {
        Register(o, true);
    }

    public static void ShouldNotNotifyWhenPropertiesChange(this INotifyPropertyChanged o)
    {
        Register(o, false);
    }

    public static void NotifyPropertyChanged(this INotifyPropertyChanged o, Action notificationAction)
    {
        if(_registeredClasses.ContainsKey(o))
        {
            bool shouldNotify = _registeredClasses.Where(x => x.Key == o).Single().Value;

            if(shouldNotify) notificationAction();
        }
    }
}

public class EntityUsingNotificationRegistry : INotifyPropertyChanged
{
    ... // all the standard INotifyPropertyChanged stuff

    string _someProperty;
    public string SomeProperty
    {
        get { return _someProperty; }
        set 
        {
            if(_someProperty == value) return;

            _someProperty = value;
            this.NotifyPropertyChanged(() => OnPropertyChanged("SomeProperty"));
        }
    }
}

public class SomethingInstantiatingOurEntity
{
    public void DoSomething()
    {
        var entity1 = new EntityUsingNotificationRegistry();
        entity1.ShouldNotifyWhenPropertiesChange();

        var entity2 = new EntityUsingNotificationRegistry();
        entity2.ShouldNotNotifyWhenPropertiesChange();

        entity1.SomeProperty = "arbitrary string"; // raises event
        entity2.SomeProperty = "arbitrary string"; // does not raise event

        var entity3 = new EntityUsingNotificationRegistry();
        entity3.SomeProperty = "arbitrary string"; // does not raise event
        entity3.ShouldNotifyWhenPropertiesChange();
        entity3.SomeProperty = "another arbitrary string"; // now raises event
    }
}

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

0 голосов
/ 18 марта 2010

Это не будет работать. Вы МОЖЕТЕ создать подкласс и внедрить его, но вам придется изменить байт-код, чтобы убедиться, что правильные методы вызываются - и это более сложный метод.

...