意见想要:拦截对列表/集合的更改

时间:2009-11-21 09:27:15

标签: c# domain-driven-design observablecollection bindinglist

虽然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核心组件的通用接口/类,我正在制作source code available(因此需要创建友好的接口)。

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

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

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


Related question

4 个答案:

答案 0 :(得分: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实现它。这绝对会遵循您界面中“最少惊喜”的政策。

答案 1 :(得分:2)

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

编辑:

您可能希望在事件之前和之后进行协同处理,例如插入,插入或VB人员使用BeforeInsert,AfterInsert。这将为用户提供更大的灵活性。

答案 2 :(得分:2)

看一下这个link,也许这就是你要找的东西,一个基于通用列表的对象,它充当一个列表但有内置事件,如BeforeItemAdded,ItemAdded,BeforeItemRemoved,ItemRemoved和ItemsCleared

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

答案 3 :(得分: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);
        }
    }
}

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