为什么通用类<t>中的List <t> .RemoveAll需要谓词的变体形式?</t> </t>

时间:2011-08-10 06:11:28

标签: c# class generics lambda

如果你在一个通用类中使用'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);
     }
}

4 个答案:

答案 0 :(得分:4)

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

请注意,只是检查参考平等是你真正想要的。

更新:我最初忘记提及这一点,但这当然意味着您将无法将其用于价值类型。另一种方法是使用类似的东西来支持两者:

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

答案 1 :(得分:2)

如果您想让代码尽可能灵活,可以像这样使用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的情况。来自documentation

  

Default属性检查类型T是否实现了   System.IEquatable(T)接口,如果是,则返回一个   EqualityComparer(T)使用该实现。否则,它   返回使用覆盖的EqualityComparer(T)   由T。

提供的Object.Equals和Object.GetHashCode

答案 2 :(得分:0)

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

System.ValueType中Equals的默认实现具有以下内容(来自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;
                }
            } 

The .net guidelines state

  

您应该考虑在值类型上实现Equals方法   因为System.ValueType上的默认实现不会   执行以及您的自定义实现。

所以我相信答案是设计人员不希望人们依赖于== for System.ValueTypes的隐式运算符,并希望他们在适用的情况下实现更好的版本。

答案 3 :(得分:0)

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

使用object.operator==

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

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

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