使用通用 BaseClass 保存对通用 ObservableCollections 的引用

Save references to generic ObservableCollections with common BaseClass

这个问题已经让我忙了半天了,我开始失去理智了:

我正在使用 Items UI 逻辑东西。有 "parent" 个项目,可以包含其他项目的 ObservableCollection。 (两者都继承自同一个ItemBase,图片节点带节点,递归排序) 为了不必在每个 "parent" 项目 class 上重新创建观察者逻辑,我想将功能添加到公共基础 class,称为 ItemBase。这个想法是,父级可以只注册它的 ObservableCollections 而 baseclass 负责事件路由和所有。问题是,我似乎无法找到一种方法来保存对这些 ObservableCollections(具有相同基数的不同类型class)的引用,以实现泛型的工作方式。

代码如下:

public abstract class ItemBase : ViewModelBase
    {
        private List<ObservableItemCollection<ItemBase>> _trackedChildItemsList = new List<ObservableItemCollection<ItemBase>>();

        public event EventHandler<ItemPropertyChangedEventArgs> ChildItemPropertyChanged;
        public event EventHandler<IsDirtyChangedEventArgs> ChildItemIsDirtyChanged;

        public override bool IsDirty
        {
            get { return base.IsDirty || AreAnyChildItemsDirty; }
            set { base.IsDirty = value; }
        }

        private bool AreAnyChildItemsDirty
        {
            get
            {
                return _trackedChildItemsList.Any(i => i.Any(l => l.IsDirty));
            }
        }

        protected void RegisterItemCollection<T>(ObservableItemCollection<T> collection)
            where T : ItemBase
        {
            _trackedChildItemsList.Add(collection); // intellisense underlines 'collection'; cannot convert from 'ObservableItemCollection<T>' to ObservableItemCollection<ItemBase>:

            collection.ItemPropertyChanged += Collection_ItemPropertyChanged;
            collection.ItemIsDirtyChanged += Collection_ItemIsDirtyChanged;
        }

        public override void Dispose()
        {
            foreach (ObservableItemCollection<ItemBase> collection in _trackedChildItemsList)
            {
                collection.ItemPropertyChanged -= Collection_ItemPropertyChanged;
                collection.ItemIsDirtyChanged -= Collection_ItemIsDirtyChanged;
            }

            base.Dispose();
        }

        private void Collection_ItemPropertyChanged(object sender, ItemPropertyChangedEventArgs e)
        {
            OnChildItemPropertyChanged(e);
        }

        protected virtual void OnChildItemPropertyChanged(ItemPropertyChangedEventArgs e)
        {
            ChildItemPropertyChanged?.Invoke(this, e);
        }

        private void Collection_ItemIsDirtyChanged(object sender, IsDirtyChangedEventArgs e)
        {
            OnItemIsDirtyChanged(e);
        }

        protected virtual void OnItemIsDirtyChanged(IsDirtyChangedEventArgs e)
        {
            ChildItemIsDirtyChanged?.Invoke(this, e);
        }
    }

如您所见,我正在使用 ObservableCollection 的派生自定义类型,即 ObservableItemCollection,它负责集合本身的 ItemPropertyChanged 和 ItemIsDirtyChanged 调用。这允许人们从外部捕获这些事件。 现在,我希望它位于一个集中位置,而不是在每个父项本身(重复)中使用 'catching the events' 逻辑,即 baseclass.

现在的主要问题是,在注册 ObservableItemCollections 时,我不可能保留对它们的引用,因为没有公共基础。 ObservableItemCollection<CustomItem> 不继承自 ObservableItemCollection<ItemBase>,因为它是一个集合。我尝试用泛型解决整个问题,但是,以上是我所得到的。它无法在我写“无法从 'ObservableItemCollection' 转换为 ObservableItemCollection”注释的地方进行编译。

我明白编译失败的原因,但是,我似乎找不到workaround/working解决方案。

我绝对需要对集合的直接引用(转换为我的自定义类型 ObservableItemCollection),否则整个事情将无法工作。您可以在代码中看到我正在访问集合本身的事件以及 ItemBase 的属性。

无论哪种方式,我似乎都找不到这些集合的共同基础。我尝试使用基于动态和反射的转换、接口、自定义通用 ParentItem 类型,但都没有用(我可能忽略了一些东西),即使这样做了,它也会很丑陋。

难道真的不能通过有限的hacking东西来实现我想要的吗?我无法相信在我投入了这么多时间之后我没有找到一个好的解决方案。

附加信息:

在父项中,我有以下 ObservableCollections:

public ObservableItemCollection<SomeItem1> Collection1 { get; set; } = new ObservableItemCollection<SomeItem1>();
public ObservableItemCollection<SomeItem2> Collection2 { get; set; } = new ObservableItemCollection<SomeItem2>();

其中两种项目类型都继承自 ItemBase。然后我在父项构造函数中调用基本方法 RegisterItemCollection,如下所示:

RegisterItemCollection(Collection1);
RegisterItemCollection(Collection2);

你不能这样做,因为如果你可以的话,你可以做类似的事情

class A {}
class B : A { }
class C : A { }

var list = new List<List<A>>();
var sublist_b = new List<B>();
sublist_b.Add(new B());
list.Add(sublist_b);
var sublist = list.Single();
sublist.Add(new C()); // <- now a List<B> contains an object that ist not if type B or derived B

我建议您只使用 ObservableItemCollection<ItemBase> 来放置您的物品。

WPF 集合控件有同样的问题:如何定义一个 属性 可以保存对任何类型的通用集合的引用?答案:使 属性 成为对所有集合实现的非通用接口的引用。这是一个非常普遍的问题,这也是非泛型 System.Collections.IEnumerableSystem.Collections.IList 在引入泛型之后这么多年仍在整个 .NET 框架中大量使用的原因。

您在 RegisterItemCollection()IsDirtyDispose() 中所做的任何事情都不需要关心集合中项目的类型。因此,采用代码与之交互所需的任何方法和属性,并将其全部放在非通用接口或基 class 中。您的基础 class 已经是通用的(ObservableCollection<T>,我想),所以使用一个接口。

public interface IObservableItemCollection
{
    event EventHandler<ItemPropertyChangedEventArgs> ItemPropertyChanged;
    event EventHandler<IsDirtyChangedEventArgs> ItemIsDirtyChanged;
    bool IsDirty { get; }
}

public interface IDirtyable
{
    //  I'm pretty sure you'll want this event here, and I think you'll want your collection to 
    //  implement IDirtyable too.
    //event EventHandler<IsDirtyChangedEventArgs> IsDirtyChanged;
    bool IsDirty { get; }
}

public class ObservableItemCollection<T>
    : ObservableCollection<T>, IObservableItemCollection
    where T : IDirtyable
{
    public bool IsDirty => this.Any(item => item.IsDirty);

    public event EventHandler<ItemPropertyChangedEventArgs> ItemPropertyChanged;
    public event EventHandler<IsDirtyChangedEventArgs> ItemIsDirtyChanged;
}

public class ViewModelBase : IDisposable, IDirtyable
{
    public virtual bool IsDirty => true;

    public virtual void Dispose()
    {
    }
}

public class ItemBase : ViewModelBase
{
    private List<IObservableItemCollection> _trackedChildItemsList = new List<IObservableItemCollection>();

    public override bool IsDirty
    {
        get
        {
            return base.IsDirty || _trackedChildItemsList.Any(coll => coll.IsDirty);
        }
    }

    protected void RegisterItemCollection<T>(ObservableItemCollection<T> collection)
                where T : ItemBase
    {
        _trackedChildItemsList.Add(collection);

        collection.ItemPropertyChanged += Collection_ItemPropertyChanged;
        collection.ItemIsDirtyChanged += Collection_ItemIsDirtyChanged;
    }

    public override void Dispose()
    {
        foreach (IObservableItemCollection collection in _trackedChildItemsList)
        {
            collection.ItemPropertyChanged -= Collection_ItemPropertyChanged;
            collection.ItemIsDirtyChanged -= Collection_ItemIsDirtyChanged;
        }

        base.Dispose();
    }

    private void Collection_ItemIsDirtyChanged(object sender, IsDirtyChangedEventArgs e)
    {
    }

    private void Collection_ItemPropertyChanged(object sender, ItemPropertyChangedEventArgs e)
    {
    }
}

public class ItemPropertyChangedEventArgs : EventArgs
{
}

public class IsDirtyChangedEventArgs : EventArgs
{
}

您也可以将 _trackedChildItemsList 设为 IDisposable 的集合,并让这些集合清除它们自己的事件处理程序,但是 a class clearing its own event handlers is pretty gruesome。当可以使用传统的 OOP 以可读和可维护的方式完成工作时,避免反射。而且您仍然需要为 IsDirty 考虑一些事情。