5

尽管BindingList<T>ObservableCollection<T>提供了检测列表更改的机制,但它们不支持在更改发生之前检测/拦截更改的机制。

我正在编写几个接口来支持这一点,但我想征求您的意见。

选项 1:列出每种操作的引发事件

在这里,消费者可能会编写如下代码:

public class Order : Entity
    {
        public Order()
        {
            this.OrderItems = new List<OrderItem>();
            this.OrderItems.InsertingItem += new ListChangingEventHandler<OrderItem>(OrderItems_InsertingItem);
            this.OrderItems.SettingItem += new ListChangingEventHandler<OrderItem>(OrderItems_SettingItem);
            this.OrderItems.RemovingItem += new ListChangingEventHandler<OrderItem>(OrderItems_RemovingItem);
        }

        virtual public List<OrderItem> OrderItems { get; internal set; }

        void OrderItems_InsertingItem(object sender, IOperationEventArgs<OrderItem> e)
        {
            if (!validationPasses)
            {
                e.Cancel = true;
                return;
            }

            e.Item.Parent = this;
        }

        void OrderItems_SettingItem(object sender, IOperationEventArgs<OrderItem> e)
        {
            if (!validationPasses)
            {
                e.Cancel = true;
                return;
            }

            e.Item.Parent = this;
        }

        void OrderItems_RemovingItem(object sender, IOperationEventArgs<OrderItem> e)
        {
            if (!validationPasses)
            {
                e.Cancel = true;
                return;
            }

            e.Item.Parent = null;
        }

    }

选项 2:列表引发单个事件,操作由事件参数确定

在这里,消费者可能会编写如下代码:

public class Order : Entity
    {
        public Order()
        {
            this.OrderItems = new List<OrderItem>();
            this.OrderItems.ListChanging += new ListChangingEventHandler<OrderItem>(OrderItems_ListChanging);
        }

        virtual public List<OrderItem> OrderItems { get; internal set; }

        void OrderItems_ListChanging(object sender, IOperationEventArgs<OrderItem> e)
        {
            switch (e.Action)
            {
                case ListChangingType.Inserting:
                case ListChangingType.Setting:
                    if (validationPasses)
                    {
                        e.Item.Parent = this;
                    }
                    else
                    {
                        e.Cancel = true;
                    }
                    break;

                case ListChangingType.Removing:
                    if (validationPasses)
                    {
                        e.Item.Parent = null;
                    }
                    else
                    {
                        e.Cancel = true;
                    } 
                    break;
            }
        }

    }

背景:我正在编写一组代表 DDD 核心组件的通用接口/类,并且我正在提供源代码(因此需要创建友好的接口)。

这个问题是关于使接口尽可能内聚,以便消费者可以在不丢失核心语义的情况下派生和实现自己的集合。

PS:请不要为每个列表建议使用AddXYZ()RemoveXYZ()方法,因为我已经打折了这个想法。

PPS:我必须包括使用 .NET 2.0 的开发人员 :)


相关问题

4

4 回答 4

5

我建议ObservableCollection<T>在适当的情况下创建一些平行的东西。具体来说,我建议遵循现有的技术来通知收集更改。就像是:

class MyObservableCollection<T> 
    : INotifyPropertyChanging,   // Already exists
      INotifyPropertyChanged,    // Already exists
      INotifyCollectionChanging, // You'll have to create this (based on INotifyCollectionChanged)
      INotifyCollectionChanged   // Already exists
{ }

这将遵循已建立的模式,以便客户已经熟悉公开的接口——其中三个接口已经存在。使用现有接口还将允许与其他现有的 .NET 技术进行更适当的交互,例如 WPF(与INotifyPropertyChangedINotifyCollectionChanged接口绑定)。

我希望INotifyCollectionChanged界面看起来像:

public interface INotifyCollectionChanged
{
    event CollectionChangingEventHandler CollectionChanging;
}

public delegate void CollectionChangingEventHandler(
    object source, 
    CollectionChangingEventArgs e
);

/// <remarks>  This should parallel CollectionChangedEventArgs.  the same
/// information should be passed to that event. </remarks>
public class CollectionChangingEventArgs : EventArgs
{
    // appropriate .ctors here

    public NotifyCollectionChangedAction Action { get; private set; }

    public IList NewItems { get; private set; }

    public int NewStartingIndex { get; private set; }

    public IList OldItems { get; private set; }

    public int OldStartingIndex { get; private set; }
}

如果您希望添加取消支持,只需向集合将读取的可写bool Cancel属性添加CollectionChangingEventArgs以确定是否执行即将发生的更改。

我想这属于您的选项 2。这是要走的路,因为要与其他监视更改集合的 .net 技术正确互操作,您无论如何都必须为INotifyCollectionChanged. 这肯定会在您的界面中遵循“最少惊喜”的政策。

于 2009-11-21T11:51:56.157 回答
2

我会推荐单独的活动。对我来说似乎更清楚。

编辑:

您可能想考虑一个前后事件,例如插入、插入或 VB 家伙有它之前插入、之后插入。这将给用户更多的灵活性。

于 2009-11-21T09:31:21.623 回答
2

看看这个链接,也许这就是你要找的,一个基于通用列表的对象,它充当一个列表,但具有内置事件,例如 BeforeItemAdded、ItemAdded、BeforeItemRemoved、ItemRemoved 和 ItemsCleared。

希望这会有所帮助,汤姆。:)

于 2009-11-21T10:22:03.980 回答
2

实际上,您会惊讶于创建这样的集合是多么容易。看看System.Collections.ObjectModel.Collection<T>。那是一个旨在用于此类事情的类。它有一些虚拟方法(每个操作一个),您可以很好地覆盖和控制它们。

我会推荐选项 1,因为它更清晰明了。

以下是您可以用于此类目的的示例:

using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;

namespace TestGround
{
    public class MyCollection<T> : Collection<T>
    {
        public class ListChangeEventArgs : EventArgs
        {
            public IEnumerable<T> ItemsInvolved { get; set;}

            public int? Index { get; set;}
        }

        public delegate void ListEventHandler(object sender, ListChangeEventArgs e);

        public event ListEventHandler Inserting;

        public event ListEventHandler Setting;

        public event ListEventHandler Clearing;

        public event ListEventHandler Removing;

        public MyCollection() : base() { }

        public MyCollection(IList<T> innerList) : base(innerList) { }

        protected override void ClearItems()
        {
            Clearing(this, new ListChangeEventArgs()
            {
                 Index = null,
                 ItemsInvolved = this.ToArray(),
            });
            base.ClearItems();
        }

        protected override void InsertItem(int index, T item)
        {
            Inserting(this, new ListChangeEventArgs()
            {
                Index = index,
                ItemsInvolved = new T[] { item },
            });
            base.InsertItem(index, item);
        }

        protected override void RemoveItem(int index)
        {
            Removing(this, new ListChangeEventArgs()
            {
                Index = index,
                ItemsInvolved = new T[] { this[index] },
            });
            base.RemoveItem(index);
        }

        protected override void SetItem(int index, T item)
        {
            Setting(this, new ListChangeEventArgs()
            {
                Index = index,
                ItemsInvolved = new T[] { item },
            });
            base.SetItem(index, item);
        }
    }
}

您还可以将 ListChangeEventArgs 修改为具有名为“Cancel”的 bool 属性,并控制是否在集合中进行更改。

如果您需要此类功能,后续事件也可能很有用。

当然,您不必使用每个集合的所有事件,或者如果确实需要,可能还有其他方法可以解决问题,具体取决于您为什么需要此功能。

编辑:

如果您真的只想验证项目并将它们的 Parent 属性设置为实体实例,您实际上可以编写一个完全执行此操作的集合,或者以另一种方式概括问题。您可以向它传递一个验证项目的委托,以及另一个告诉它在添加或删除项目时要做什么。

例如,您可以使用 Action 委托来实现此目的。

你可以这样消费它:

class Order : Entity
{
    public Order()
    {
        OrderItems = new MyCollection2<OrderItem>(
            //Validation action
            item => item.Name != null && item.Name.Length < 20,
            //Add action
            item => item.Parent = this,
            //Remove action
            item => item.Parent = null
        );
    }

    ...
}

这种方法的主要好处是您不必为事件处理程序或委托而烦恼,因为您需要的所有内容都可以使用 lambda 表达式编写,但是如果您需要更高级的东西,您总是可以使用真正的委托而不是他们。

这是集合的一个例子:

public class MyCollection2<T> : Collection<T>
{
    public Func<T, bool> Validate { get; protected set; }

    public Action<T> AddAction { get; protected set; }

    public Action<T> RemoveAction { get; protected set; }

    public MyCollection2(Func<T, bool> validate, Action<T> add, Action<T> remove)
        : base()
    {
        Validate = Validate;
        AddAction = add;
        RemoveAction = remove;
    }

    protected override void ClearItems()
    {
        foreach (var item in this)
        {
            RemoveAction(item);
        }
        base.ClearItems();
    }

    protected override void InsertItem(int index, T item)
    {
        if (Validate(item))
        {
            AddAction(item);
            base.InsertItem(index, item);
        }
    }

    protected override void RemoveItem(int index)
    {
        RemoveAction(this[index]);
        base.RemoveItem(index);
    }

    protected override void SetItem(int index, T item)
    {
        if (Validate(item))
        {
            RemoveAction(this[index]);
            AddAction(item);
            base.SetItem(index, item);
        }
    }
}

出于这样的目的,我认为这是最干净的方法。

于 2009-11-21T10:59:42.143 回答