开发者

Injecting (INotifyPropertyChanged functionality) to an instance of an class

I have a class that implements INotifyPropertyChanged. I create an instance of a class in some viewModel. Is it possible to remove this functionality from the class and inject it after the instance was created? I heard that ICustomTypeDescriptor would make this happen, but i dont know how to use it.

public class C : ICustomNotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public int _id;
    public string开发者_JAVA技巧 _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));
        }
    }


If you are just trying to prevent the notifications from being fired when the object is first created and properties set, you can add boolean flag(s) that is/are false until the properties have been set once. You only execute the notification if the flag is true.

Edit:

I don't think there's a clean way to get the functionality in there after removing all the INotifyPropertyChanged code, but there are many ways to control the functionality from outside the instance.

Please note that I wrote all this code in the text editor, not in VisualStudio; it has not been tested in any way.

Add a method to enable notifications:

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

You could do the same thing without the method, if you knew at the time of instantiation whether or not the instance should produce notifications, in which case you'd just need a boolean parameter in the constructor.

Another variation would be to use the Factory pattern, where your Factory has internal access to the boolean flag and sets it upon construction.

Encapsulate the condition in a proxy:

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

Here your consuming classes get the entity proxy instead of the entity itself (but is none the wiser because it references only IEntity when you program to interfaces/abstractions). The wrapping of the proxy can happen in a factory or through an IoC container/DI framework.

The main advantage to this approach is that your entity maintains a pure INotifyPropertyChanged implementation, and the conditional aspect is handled from without. Another advantage is that it helps to enforce programming to abstractions and inversion of control.

The main disadvantage is that you'll need to create proxies for each INotifyPropertyChanged implementation that you want to have this conditional behaviour.

Create a registry to keep track of what instances should or should not raise notifications:

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

Now, the registry has a distinct shortcoming in that it holds references to every instance and will prevent those instances from being picked up by the garbage collector. There may be a solution to this by implementing the registry with WeakReferences, but I'm not up-to-snuff on their usage to recommend a particular implementation.


This will not work. You COULD subclass and inject it, but you would have to change the byte-code to make sure the proper methods are CALLED - and that is the harder method.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜