虽然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的开发人员:)
答案 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(与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
实现它。这绝对会遵循您界面中“最少惊喜”的政策。
答案 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);
}
}
}
出于这样的目的,我认为这是最干净的方法。