【问题标题】:Detach Event Handler From Dynamic Object从动态对象中分离事件处理程序
【发布时间】:2019-11-09 17:28:45
【问题描述】:

我正在尝试使用 dynamic 对象分离 event handler。我没有经常使用dynamic,而且我不确定我在哪里出错了。我收到的例外是:

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException

“object”不包含“CollectionChanged”的定义

[Fact]
public void Test()
{
    var foo = new Foo();
    foo.Bars = new ObservableCollection<Bar>();
    foo.ClearDelegates();
}
Dictionary<string, object> _values;
Dictionary<string, NotifyCollectionChangedEventHandler> _collectionChangedDelegates;

public void ClearDelegates()
{
    foreach (var kvp in _values)
    {
        var currentValue = _values[kvp.Key];
        if (currentValue == null)
            continue;

        var type = currentValue.GetType();

        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ObservableCollection<>))
        {
            dynamic observableCollection = currentValue;
            observableCollection.CollectionChanged -= _collectionChangedDelegates[kvp.Key];
        }
    }
}
class Foo : DomainObject
{
    public ObservableCollection<Bar> Bars
    {
        get { return GetValue<ObservableCollection<Bar>>(nameof(Bars)); }
        set { SetValue(nameof(Bars), value); }
    }
}
class DomainObject
{
    Dictionary<string, object> _values = new Dictionary<string, object>();

    Dictionary<string, NotifyCollectionChangedEventHandler> _collectionChangedDelegates =
        new Dictionary<string, NotifyCollectionChangedEventHandler>();


    public void ClearDelegates()
    {
        foreach (var kvp in _values)
        {
            var currentValue = _values[kvp.Key];
            if (currentValue == null)
                continue;

            var type = currentValue.GetType();

            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ObservableCollection<>))
            {
                dynamic observableCollection = currentValue;
                observableCollection.CollectionChanged -= _collectionChangedDelegates[kvp.Key];
            }
        }

        _collectionChangedDelegates.Clear();
    }


    protected T GetValue<T>(string propertyName)
    {
        return (T)_values[propertyName];
    }

    protected void SetValue<T>(string propertyName, ObservableCollection<T> value)
    { 
        if (value != null)
            HookupCollectionDelegates(propertyName, value);
        
        Set(propertyName, value);
    }

    protected void SetValue<T>(string propertyName, T value)
    {
        Set(propertyName, value);
    }


    void Set<T>(string propertyName, T value)
    {
        _values[propertyName] = value;
        OnPropertyChanged(propertyName);
    }

    void HookupCollectionDelegates<T>(string propertyName, ObservableCollection<T> collection)
    {
        var collectionChangedDelegate = delegate(object sender, NotifyCollectionChangedEventArgs e)
        {
            // do work
        };
        collection.CollectionChanged += collectionChangedDelegate;

        if (_collectionChangedDelegates.ContainsKey(propertyName))
            _collectionChangedDelegates[propertyName] = collectionChangedDelegate;
        else
            _collectionChangedDelegates.Add(propertyName, collectionChangedDelegate);
    }
}

【问题讨论】:

  • 您的代码运行良好。 dotnetfiddle.net。你能展示你抛出异常的用法吗?
  • @Alex 那是文字复制粘贴。我公开了该方法,并创建了一个人为的测试,编辑了主要帖子。结果相同。 Bars 的设置器只是插入到值字典中。但我看到小提琴确实按预期执行......

标签: c# dynamic reflection


【解决方案1】:

我也喜欢新功能,但我们不应该忘记旧功能。你的字典看起来像一个关系。我想现在是Join 时间,所以请帮助我们LINQ

Dictionary<string, object> _values;
Dictionary<string, NotifyCollectionChangedEventHandler> _collectionChangedDelegates;

void ClearDelegates()
{
    foreach (var i in
        _values.Join(_collectionChangedDelegates,
                     outer => outer.Key,
                     inner => inner.Key,
                     (outer, inner) =>
                     new
                     {
                         Target = (INotifyCollectionChanged)outer.Value,
                         EventHandler = inner.Value
                     })) // expected, you manage your dictionaries carefully
        i.Target.CollectionChanged -= i.EventHandler;

    _collectionChangedDelegates.Clear();
}

【讨论】:

  • 好眼光!这绝对是一个参考完整性约束。
【解决方案2】:

为避免run-time dynamic 绑定问题,只需保持强类型。请注意,CollectionChanged 事件是在 INotifyCollectionChanged 中定义的,而不是特别是 ObservableCollection&lt;T&gt;,因此您实际上不需要需要 dynamic。此代码有助于在compile-time避免问题

Dictionary<string, object> _values;
Dictionary<string, NotifyCollectionChangedEventHandler> _collectionChangedDelegates;

void ClearDelegates()
{
    foreach(var key in _values.Keys)
        if (_values[key] is INotifyCollectionChanged value && 
            _collectionChangedDelegates.TryGetValue(key, out var handler))
            value.CollectionChanged -= handler;
    _collectionChangedDelegates.Clear();
}

【讨论】:

  • 不幸的是,值字典包含所有类型的值,这就是为什么它被键入为对象。然而,意识到事件是在接口上声明的,而不是在 ObservableCollection 类上,解决了这个问题。我可以只测试接口并轻松转换,因为无需担心通用参数。谢谢!
  • @Michael,不客气。我根据你的笔记更新了答案。
  • 看看它的短小精悍与花哨的模式匹配功能。
  • 模式匹配非常棒。期待 C# 8 的改进。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多