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: Списки поднять одно событие, и действие определяется из события args

Здесь потребители могут написать такой код:

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.

+0

В первом случае вы можете использовать один и тот же обработчик для InsertingItem и SettingItem – RichardOD

ответ

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

Это будет следовать установленной модель, так что клиенты уже знакомы с обнаженным interfaces-- трех интерфейсов уже существуют. Использование существующих интерфейсов также позволит более правильное взаимодействие с другими уже существующими технологиями .NET, такие как WPF

Я ожидаю, что интерфейс INotifyCollectionChanged смотреть что-то вроде (который связывается с интерфейсами 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, которые контролируют изменение коллекций, вам все равно придется реализовать его для INotifyCollectionChanged. Это обязательно будет следовать политике «Наименьший сюрприз» в вашем интерфейсе.

+0

Спасибо, Грег. «Принцип наименьшего Suprise» был определенно причиной варианта 2. В моих стремлениях сохранить совместимость с .NET 2.0 мне пришлось бы использовать событие ListChanged (INotifyCollectionChanged поставляется с .NET 3.0). ListChanged предоставляет перечисление ListChangedType, которое точно не соответствует моим требованиям. Добавляя * больше * интерфейсов/перечислений в микс, я боюсь, что разработчики будут отчуждены моей библиотекой :( –

2

Я бы рекомендовал отдельные мероприятия. Мне кажется более понятным.

EDIT:

Вы можете cosider до и после события, такие как вставка, вставленных или как ребята VB есть это BeforeInsert, AfterInsert. Это даст пользователю большую гибкость.

+0

Вы не думаете, что это было бы утомительно, нужно добавить все эти обработчики событий для * каждого * списка в сущности? –

+0

@Vijay: Разделение событий может облегчить потребителям явное игнорирование событий, которые им не интересны. Хорошая работа у вас есть! –

+0

Нет, это позволит мне регистрировать слушателей для конкретных событий, которые мне нужны, без общего блока переключателей. –

2

Посмотрите на этот link, возможно, это то, что вы ищете, объект на основе общего списка, который действует как список, но со встроенными событиями, такими как BeforeItemAdded, ItemAdded, BeforeItemRemoved, ItemRemoved и ItemsCleared.

Надеюсь, это поможет, Том. :)

+0

Я бы рекомендовал против такого подхода. Он неправильно обрабатывает ситуации, когда изменяется диапазон значений, например, что приводит к повторной реализации чего-то, что база уже выставила лучше. –

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 иметь свойство Ьоо с именем «Отмена», а также контролировать Wheter делать изменения или нет в сборнике.

Последующие события также могут быть полезны, если вам нужна такая функциональность.

Конечно, вам не придется использовать все события в каждой коллекции, или если это действительно необходимо, могут быть другие способы решения проблемы в зависимости от того, зачем вам нужна эта функциональность.

EDIT:

Если вы действительно хотите только проверить элементы и установить их 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 
     ); 
    } 

    ... 
} 

Основное преимущество этого подхода заключается в том, что вам не придется возиться с обработчиков событий или делегатов, beacuse все, что вам нужно можно записать с помощью лямбда-выражения , однако, если вам нужно что-то более продвинутое, вы всегда можете использовать реальный делегат вместо них.

Это пример коллекции:

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

Для таких целей, я думаю, что это самый чистый путь.

+0

Спасибо за ответ. Фактически, у моей библиотеки уже есть базовые классы, которые реализуют функциональность. Этот вопрос заключается в том, чтобы сделать интерфейс максимально связным, чтобы потребители могли получать и реализовывать свои собственные коллекции, не теряя основную семантику. –

+0

Я отредактировал сообщение, чтобы добавить еще один вариант для вас, без всех событий. Это специализированный подход, который избавит вас от необходимости связывать все обработчики событий всех коллекций в каждом объекте. – Venemo

+0

Но, если вы хотите получить общее решение, и вы не против беспокоиться обо всех обработчиках событий, я думаю, что вариант 1 - это правильный путь. – Venemo

 Смежные вопросы

  • Нет связанных вопросов^_^