C# List<T> 在更改时触发事件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12785019/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 00:27:23  来源:igfitidea点击:

List<T> firing Event on Change

c#.net

提问by Martin

I created a Class EventListinheriting Listwhich fires an Event each time something is Added, Inserted or Removed:

我创建了一个类EventList继承List,它在每次添加、插入或删除内容时触发一个事件:

public class EventList<T> : List<T>
{
    public event ListChangedEventDelegate ListChanged;
    public delegate void ListChangedEventDelegate();

    public new void Add(T item)
    {
        base.Add(item);
        if (ListChanged != null
            && ListChanged.GetInvocationList().Any())
        {
            ListChanged();
        }
    }
    ...
}

At the Moment I use it as a Property like this:

目前我将它用作这样的属性:

public EventList List
{
    get { return m_List; }
    set
    {
        m_List.ListChanged -= List_ListChanged;

        m_List = value;

        m_List.ListChanged += List_ListChanged;
        List_ListChanged();
    }
}

Now my Problem is, can I somehow handle if a new Object is referred to it or prevent that, so I do not have to do the event wiring stuff in the setter?

现在我的问题是,如果一个新对象被引用或阻止它,我可以以某种方式处理它,所以我不必在 setter 中进行事件连接吗?

Of course, I can change the property to "private set" but I would like to be able to use the class as variable as well.

当然,我可以将属性更改为“私有集”,但我也希望能够将该类用作变量。

采纳答案by Patrick

You seldom create a new instance of a collection class in a class. Instantiate it once and clear it instead of creating a new list. (and use the ObservableCollection since it already has the INotifyCollectionChangedinterface inherited)

您很少在类中创建集合类的新实例。实例化一次并清除它而不是创建一个新列表。(并使用 ObservableCollection 因为它已经继承了INotifyCollectionChanged接口)

private readonly ObservableCollection<T> list;
public ctor() {
    list = new ObservableCollection<T>();
    list.CollectionChanged += listChanged;
}

public ObservableCollection<T> List { get { return list; } }

public void Clear() { list.Clear(); }

private void listChanged(object sender, NotifyCollectionChangedEventArgs args) {
   // list changed
}

This way you only have to hook up events once, and can "reset it" by calling the clear method instead of checking for null or equality to the former list in the set accessor for the property.

通过这种方式,您只需要连接一次事件,并且可以通过调用 clear 方法“重置它”,而不是在属性的 set 访问器中检查 null 或与前一个列表的相等性。



With the changes in C#6 you can assign a get property from a constructor without the backing field (the backing field is implicit)

通过 C#6 中的更改,您可以从没有支持字段的构造函数中分配 get 属性(支持字段是隐式的)

So the code above can be simplified to

所以上面的代码可以简化为

public ctor() {
    List = new ObservableCollection<T>();
    List.CollectionChanged += OnListChanged;
}

public ObservableCollection<T> List { get; }

public void Clear()
{
    List.Clear();
}

private void OnListChanged(object sender, NotifyCollectionChangedEventArgs args)
{
   // react to list changed
}

回答by paparazzo

ObservableCollection is a List with a CollectionChanged event

ObservableCollection 是一个带有 CollectionChanged 事件的列表

ObservableCollection.CollectionChanged Event

ObservableCollection.CollectionChanged 事件

For how to wire up the event handler see answer from Patrick. +1

有关如何连接事件处理程序,请参阅 Patrick 的回答。+1

Not sure what you are looking for but I use this for a collection with one event that fires on add, remove, and change.

不确定您在寻找什么,但我将其用于具有一个在添加、删除和更改时触发的事件的集合。

public class ObservableCollection<T>: INotifyPropertyChanged
{
    private BindingList<T> ts = new BindingList<T>();

    public event PropertyChangedEventHandler PropertyChanged;

    // This method is called by the Set accessor of each property. 
    // The CallerMemberName attribute that is applied to the optional propertyName 
    // parameter causes the property name of the caller to be substituted as an argument. 
    private void NotifyPropertyChanged( String propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public BindingList<T> Ts
    {
        get { return ts; }
        set
        {
            if (value != ts)
            {
                Ts = value;
                if (Ts != null)
                {
                    ts.ListChanged += delegate(object sender, ListChangedEventArgs args)
                    {
                        OnListChanged(this);
                    };
                }
                NotifyPropertyChanged("Ts");
            }
        }
    }

    private static void OnListChanged(ObservableCollection<T> vm)
    {
        // this will fire on add, remove, and change
        // if want to prevent an insert this in not the right spot for that 
        // the OPs use of word prevent is not clear 
        // -1 don't be a hater
        vm.NotifyPropertyChanged("Ts");
    }

    public ObservableCollection()
    {
        ts.ListChanged += delegate(object sender, ListChangedEventArgs args)
        {
            OnListChanged(this);
        };
    }
}

回答by jp2code

If you do not want to or can not convert to an Observable Collection, try this:

如果您不想或无法转换为 Observable 集合,请尝试以下操作:

public class EventList<T> : IList<T> /* NOTE: Changed your List<T> to IList<T> */
{
  private List<T> list; // initialize this in your constructor.
  public event ListChangedEventDelegate ListChanged;
  public delegate void ListChangedEventDelegate();

  private void notify()
  {
      if (ListChanged != null
          && ListChanged.GetInvocationList().Any())
      {
        ListChanged();
      }
  }

  public new void Add(T item)
  {
      list.Add(item);
      notify();
  }

  public List<T> Items {
    get { return list; } 
    set {
      list = value; 
      notify();
    }
  }
  ...
}

Now, for your property, you should be able to reduce your code to this:

现在,对于您的财产,您应该能够将代码缩减为:

public EventList List
{
  get { return m_List.Items; }
  set
  {
      //m_List.ListChanged -= List_ListChanged;

      m_List.Items = value;

      //m_List.ListChanged += List_ListChanged;
      //List_ListChanged();
  }
}

Why? Setting anything in the EventList.Items will call your private notify()routine.

为什么?在 EventList.Items 中设置任何内容都会调用您的私人notify()例程。

回答by Daniel

I have a Solution for when someone calls the Generic method from IList.add(object). So that you also get notified.

当有人从 IList.add(object) 调用通用方法时,我有一个解决方案。这样你也能得到通知。

using System;
using System.Collections;
using System.Collections.Generic;

namespace YourNamespace
{
    public class ObjectDoesNotMatchTargetBaseTypeException : Exception
    {
        public ObjectDoesNotMatchTargetBaseTypeException(Type targetType, object actualObject)
            : base(string.Format("Expected base type ({0}) does not match actual objects type ({1}).",
                targetType, actualObject.GetType()))
        {
        }
    }

    /// <summary>
    /// Allows you to react, when items were added or removed to a generic List.
    /// </summary>
    public abstract class NoisyList<TItemType> : List<TItemType>, IList
    {
        #region Public Methods
        /******************************************/
        int IList.Add(object item)
        {
            CheckTargetType(item);
            Add((TItemType)item);
            return Count - 1;
        }

        void IList.Remove(object item)
        {
            CheckTargetType(item);
            Remove((TItemType)item);
        }

        public new void Add(TItemType item)
        {
            base.Add(item);
            OnItemAdded(item);
        }

        public new bool Remove(TItemType item)
        {
            var result = base.Remove(item);
            OnItemRemoved(item);
            return result;
        }
        #endregion

        # region Private Methods
        /******************************************/
        private static void CheckTargetType(object item)
        {
            var targetType = typeof(TItemType);
            if (item.GetType().IsSubclassOf(targetType))
                throw new ObjectDoesNotMatchTargetBaseTypeException(targetType, item);
        }
        #endregion

        #region Abstract Methods
        /******************************************/
        protected abstract void OnItemAdded(TItemType addedItem);

        protected abstract void OnItemRemoved(TItemType removedItem);
        #endregion
    }
}

回答by Nicolas Bodin-Ripert

If an ObservableCollection is not the solution for you, you can try that:

如果 ObservableCollection 不是您的解决方案,您可以尝试:

A) Implement a custom EventArgs that will contain the new Count attribute when an event will be fired.

A) 实现一个自定义的 EventArgs,它将在触发事件时包含新的 Count 属性。

public class ChangeListCountEventArgs : EventArgs
{
    public int NewCount
    {
        get;
        set;
    }

    public ChangeListCountEventArgs(int newCount)
    {
        NewCount = newCount;
    }
}

B) Implement a custom List that inherits from List and redefine the Count attribute and the constructors according to your needs:

B) 实现一个从 List 继承的自定义 List 并根据您的需要重新定义 Count 属性和构造函数:

public class CustomList<T> : List<T>
{
    public event EventHandler<ChangeListCountEventArgs> ListCountChanged;

    public new int Count
    {
        get
        {
            ListCountChanged?.Invoke(this, new ChangeListCountEventArgs(base.Count));
            return base.Count;
        }
    }

    public CustomList()
    { }

    public CustomList(List<T> list) : base(list)
    { }

    public CustomList(CustomList<T> list) : base(list)
    { }
}

C) Finally subscribe to your event:

C) 最后订阅您的活动:

var myList = new CustomList<YourObject>();
myList.ListCountChanged += (obj, e) => 
{
    // get the count thanks to e.NewCount
};