代码之家  ›  专栏  ›  技术社区  ›  Vijay Patel

需要的意见:截取对列表/集合的更改

  •  5
  • Vijay Patel  · 技术社区  · 15 年前

    虽然 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 (因此需要创建友好的接口)。

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

    附言:请不要建议使用 AddXYZ() RemoveXYZ() 每个列表的方法,因为我已经忽略了这个想法。

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


    Related question .

    4 回复  |  直到 14 年前
        1
  •  5
  •   Greg D    15 年前

    我建议创造一些与 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)进行更适当的交互(wpf与 INotifyPropertyChanged 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技术(用于监视不断变化的集合)进行正确的互操作,您无论如何都必须为 不完整收集已更改 . 这肯定会遵循界面中的“最小惊喜”策略。

        2
  •  2
  •   Adriaan Stander    15 年前

    我建议分开处理。我觉得更清楚。

    编辑:

    您可能希望在插入、插入等前后对事件进行cosider排序,或者像vb成员在插入之前、插入之后对事件进行cosider排序一样。这将给用户更多的灵活性。

        3
  •  2
  •   t0mm13b    15 年前

    看看这个 link ,可能这就是您要查找的,一个基于列表的通用对象,它充当列表,但具有内置事件,如beforeitemadded、itemadded、beforeitemremoved、itemremoved和itemseared。

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

        4
  •  2
  •   Venemo    15 年前

    实际上,你会惊讶于你能如此轻松地创建一个这样的集合。 看一看 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属性,并控制Wheter来进行更改或不在集合中。

    如果您需要这样的功能,After事件也很有用。

    当然,您不必使用每个集合的所有事件,或者如果确实有必要,根据您需要此功能的原因,可能有其他方法来解决问题。

    编辑:

    如果您真的只想验证这些项并将它们的父属性设置为实体实例,那么实际上您可以编写一个这样做的集合,或者用另一种方式概括问题的集合。您可以向它传递一个委托来验证项目,另一个委托告诉它在添加或删除项目时应该做什么。

    例如,您可以使用操作委托来实现这一点。

    你可以这样消费它:

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

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