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:.NET2.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
{ }

これは確立されたパターンに従うため、クライアントはすでに公開されているインターフェイスに慣れているため、インターフェイスのうち 3 つが既に存在します。INotifyPropertyChanged既存のインターフェイスを使用すると、WPF (およびINotifyCollectionChangedインターフェイスにバインドする) など、他の既存の .NET テクノロジとのより適切な対話も可能になります。

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

このリンクを見てください。おそらくそれがあなたが探しているものです。リストとして機能するが、BeforeItemAdded、ItemAdded、BeforeItemRemoved、ItemRemoved、ItemsClearedなどの組み込みイベントを持つジェネリックリストベースのオブジェクトです。

トム、これがお役に立てば幸いです。:)

于 2009-11-21T10:22:03.980 に答える
2

別のイベントをお勧めします。私にはもっとはっきりしているようです。

編集:

Inserting、Insertedなどのイベントの前後、またはVBの人がBeforeInsert、AfterInsertを持っているので、検討することをお勧めします。これにより、ユーザーの柔軟性が高まります。

于 2009-11-21T09:31:21.623 に答える
2

実際、そのようなコレクションを簡単に作成できることに驚かれることでしょう。をご覧くださいSystem.Collections.ObjectModel.Collection<T>。それは、そのようなことに使用することを意図したクラスです。いくつかの仮想メソッド (操作ごとに 1 つ) があり、オーバーライドしてうまく制御できます。

オプション 1 の方が明確でわかりやすいため、オプション 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
        );
    }

    ...
}

このアプローチの主な利点は、ラムダ式を使用して必要なものをすべて記述できるため、イベント ハンドラーやデリゲートを気にする必要がないことです。彼ら。

これはコレクションの例です:

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 に答える