征求意见:拦截列表/集合的更改

发布于 2024-08-12 04:47:19 字数 3671 浏览 7 评论 0 原文

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

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

选项 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()< /code> 和 RemoveXYZ() 方法用于每个列表,因为我已经不考虑这个想法了。

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


相关问题

Although BindingList<T> and ObservableCollection<T> provide mechanisms to detect list changes, they don't support mechanisms to detect/intercept changes before they happen.

I'm writing a couple of interfaces to support this, but I want to canvas your opinion.

Option 1: Lists raise events for each type of action

Here, consumers might write code like this:

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

    }

Option 2: Lists raise a single event, and the action is determined from the event args

Here, consumers might write code like this:

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

    }

Background: I'm writing a set of general purpose interfaces/classes that represent the core components of DDD, and I'm making the source code available (hence the need to create friendly interfaces).

This question is about making the interface as cohesive as possible, so that consumers can derive and implement their own collections without losing the core semantics.

PS: Please don't suggest using AddXYZ() and RemoveXYZ() methods for each list, because I've already discounted that idea.

PPS: I must include developers using .NET 2.0 :)


Related question.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

演多会厌 2024-08-19 04:47:19

我建议在适当的情况下创建与 ObservableCollection 并行的东西。具体来说,我建议遵循现有的收集变更通知技术。类似于:

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

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

我希望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 实现它。这肯定会遵循界面中“最少惊喜”的政策。

I would suggest creating something that parallels the ObservableCollection<T> where appropriate. Specifically, I would suggest following the existing techniques for notification of change of collection. Something like:

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

This will follow established patterns so that clients are already familiar with the exposed interfaces-- three of the interfaces already exist. The use of existing interfaces will also allow more proper interaction with other already existing .NET technologies, such as WPF (which binds against the INotifyPropertyChanged and INotifyCollectionChanged interfaces.)

I would expect the INotifyCollectionChanged interface to look something like:

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

If you wish to add cancellation support, simply add a writable bool Cancel property to CollectionChangingEventArgs that the collection will read to determine whether to execute the change that's about to occur.

I suppose this falls under your Option 2. This is the way to go because, to interoperate properly with other .net technologies that monitor changing collections, you're going to have to implement it anyway for INotifyCollectionChanged. This will definitely follow the policy of "Least Surprise" in your interface.

空城仅有旧梦在 2024-08-19 04:47:19

我会推荐单独的活动。我觉得更清楚了。

编辑:

您可能需要考虑前后事件,例如插入、插入或 VB 人员拥有的 BeforeInsert、AfterInsert。这将为用户提供更大的灵活性。

I would recomend seperate events. It seems more clear to me.

EDIT:

You might want to cosider a before and after event such as Inserting,Inserted or as the VB guys have it BeforeInsert, AfterInsert. This will give the user more flexability.

灯下孤影 2024-08-19 04:47:19

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

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

Have a look at this link, maybe that is what you are looking for, a Generic List based object that acts as a List but with built-in events such as BeforeItemAdded, ItemAdded, BeforeItemRemoved, ItemRemoved and ItemsCleared.

Hope this helps, Tom. :)

恋竹姑娘 2024-08-19 04:47:19

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

我推荐选项 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);
        }
    }
}

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

Actually, you will be surprised how easily you can create a collection like that.
Take a look at System.Collections.ObjectModel.Collection<T>. That is a class which is intended to be used for such things. It has a few virtual methods (one for every operation) which you can override and control very well.

I would recommend Option 1, since it is more clear and straightforward.

Here is an example which you can use for such purposes:

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

You could also modify the ListChangeEventArgs to have a bool property with the name "Cancel", and control wheter to do the change or not in the collection.

The after events could also be useful, if you need such functionality.

Of course, you won't have to use all events of every collections, or if it is really necessary, there may be other ways to solve the problem depending on why do you need this functionality.

EDIT:

If you really only want to validate the items and set their Parent property to an entity instance, you can actually write a collection which does exactly that, or something that generalizes the problem in another way. You could pass it a delegate which validates the item, and and another which tells it what to do when an item is added or removed.

For example, you can achieve this using the Action delegate.

You could consume it this way:

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

    ...
}

The major benefit of this approach is that you don't have to bother with event handlers or delegates, beacuse all that you need can be written using lambda expressions, however if you need something more advanced, you can always use a real delegate instead of them.

This is an example of the collection:

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

For such purposes, I think this is the cleanest way to go.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文