开发者

why does List<T>.RemoveAll in a generic class<T> require a variant form of Predicate?

If you use 'RemoveAll' inside a generic class that you intend to be used t开发者_Python百科o hold a collection of any type object, like this:

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


Well since T can be a value type you would have to do something like this:

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

Be careful though that just checking for refrence equality is what you actually want.

UPDATE: I forgot to mention this initially but this will of course mean that your will not be able to use it for value types. An alternative would be using something like this to sort of support both:

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


If you want to make the code as flexible as possible, you can use EqualityComparer<T>.Default like this:

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

That code will work for any type of T (including nullable types and value types), it avoids boxing and it will also handle cases where T implements IEquatable<T> or overrides object.Equals. From the documentation:

The Default property checks whether type T implements the System.IEquatable(T) interface and, if so, returns an EqualityComparer(T) that uses that implementation. Otherwise, it returns an EqualityComparer(T) that uses the overrides of Object.Equals and Object.GetHashCode provided by T.


It must work for all kinds of T Reference and ValueTypes.

The default implementation for Equals in the System.ValueType has the following (from the ms reference source)

            // 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:

You should consider implementing the Equals method on value types because the default implementation on System.ValueType will not perform as well as your custom implementation.

So I believe the answer is that the designers don't want people to rely on an implicit operator for == for System.ValueTypes, and want them to implement better versions where this is applicable.


operator== is bound by the compiler and requires that both arguments have the same compile time type.

Either use explicit reference comparison by using object.operator==:

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

or Equals (which may be overridden and considers the runtime type)

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜