为什么List泛型类中的RemoveAll <需要谓词的变体形式

本文关键字:谓词 RemoveAll 泛型类 为什么 List | 更新日期: 2023-09-27 18:05:08

如果你在一个泛型类中使用'RemoveAll',你打算用它来保存任何类型对象的集合,像这样:

public class SomeClass<T>
{
     internal List<T> InternalList;
     public SomeClass() { InternalList = new List<T>(); }
     public void RemoveAll(T theValue)
     {
       //  this will work
       InternalList.RemoveAll(x => x.Equals(theValue));
       // the usual form of Lambda Predicate 
       // for RemoveAll will not compile
       // error: Cannot apply operator '==' to operands of Type 'T' and 'T'
       // InternalList.RemoveAll(x => x == theValue);
     }
}

为什么List<T>泛型类中的RemoveAll <需要谓词的变体形式

既然T可以是一个值类型,那么你就必须这样做:

  public class SomeClass<T> where T : class
  { 
    internal List<T> InternalList;
    public SomeClass() { InternalList = new List<T>(); }
    public void RemoveAll(T theValue)
    {
        //  this will work
        InternalList.RemoveAll(x => x == theValue);
    }
  }

要小心,检查引用是否相等才是你真正想要的。

UPDATE:我最初忘记提到这一点,但这当然意味着你将无法将其用于值类型。另一种选择是使用类似这样的东西来同时支持:

public abstract class SomeCollection<T>
{
    internal List<T> InternalList;
    public SomeCollection() { InternalList = new List<T>(); }
    public abstract void RemoveAll(T theValue);
}
public class ReferenceCollection<T> : SomeCollection<T> where T : class
{
    public override void RemoveAll(T theValue)
    {
        InternalList.RemoveAll(x => x == theValue);
    }
}
public class ValueCollection<T> : SomeCollection<T> where T : struct
{
    public override void RemoveAll(T theValue)
    {
        InternalList.RemoveAll(x => x.Equals(theValue));
    }
}

如果你想让代码尽可能灵活,你可以这样使用EqualityComparer<T>.Default:

public void RemoveAll(T theValue)
{
    //  this will work
    InternalList.RemoveAll(x => EqualityComparer<T>.Default.Equals(x, theValue));
}

该代码将适用于任何类型的T(包括可空类型和值类型),它避免了装箱,它也将处理T实现IEquatable<T>或覆盖object.Equals的情况。来自文档:

Default属性检查类型T是否实现System.IEquatable(T)接口,如果是,则返回一个使用那个实现的EqualityComparer(T)。否则,它返回一个EqualityComparer(T),它使用的是的重写对象。等号和对象。

提供的GetHashCode

它必须适用于所有类型的T Reference和valuetype。

系统中Equals的默认实现。ValueType有以下内容(来自ms参考源)

            // if there are no GC references in this object we can avoid reflection
            // and do a fast memcmp 
            if (CanCompareBits(this))
                return FastEqualsCheck(thisObj, obj); 
            FieldInfo[] thisFields = thisType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            for (int i=0; i<thisFields.Length; i++) {
                thisResult = ((RtFieldInfo)thisFields[i]).InternalGetValue(thisObj,false);
                thatResult = ((RtFieldInfo)thisFields[i]).InternalGetValue(obj, false);
                if (thisResult == null) {
                    if (thatResult != null) 
                        return false; 
                }
                else 
                if (!thisResult.Equals(thatResult)) {
                    return false;
                }
            } 

.net指南声明:

你应该考虑在值类型上实现Equals方法因为System上的默认实现。ValueType则不会执行和您的自定义实现一样好。

所以我相信答案是设计者不希望人们依赖于System ==的隐式操作符。

operator==受编译器约束,要求两个参数具有相同的编译时类型。

通过使用object.operator==:

使用显式引用比较
   InternalList.RemoveAll(x => (object)x == (object)theValue);

Equals(可能被覆盖并考虑运行时类型)

   InternalList.RemoveAll(x => x.Equals(theValue));