linq-Contains
源码:
public static bool Contains<TSource>(this IEnumerable<TSource> source, TSource value) { ICollection<TSource> collection = source as ICollection<TSource>; if (collection != null) return collection.Contains(value); return Contains<TSource>(source, value, null); } public static bool Contains<TSource>(this IEnumerable<TSource> source, TSource value, IEqualityComparer<TSource> comparer) { if (comparer == null) comparer = EqualityComparer<TSource>.Default; if (source == null) throw Error.ArgumentNull("source"); foreach (TSource element in source) if (comparer.Equals(element, value)) return true; return false; }
用来判断整个集合中元素是否都包含指定字符串。EqualityComparer源码如下:
[Serializable] [TypeDependencyAttribute("System.Collections.Generic.ObjectEqualityComparer`1")] public abstract class EqualityComparer<T> : IEqualityComparer, IEqualityComparer<T> { static readonly EqualityComparer<T> defaultComparer = CreateComparer(); public static EqualityComparer<T> Default { get { Contract.Ensures(Contract.Result<EqualityComparer<T>>() != null); return defaultComparer; } } // // Note that logic in this method is replicated in vm\compile.cpp to ensure that NGen // saves the right instantiations // [System.Security.SecuritySafeCritical] // auto-generated private static EqualityComparer<T> CreateComparer() { Contract.Ensures(Contract.Result<EqualityComparer<T>>() != null); RuntimeType t = (RuntimeType)typeof(T); // Specialize type byte for performance reasons if (t == typeof(byte)) { return (EqualityComparer<T>)(object)(new ByteEqualityComparer()); } // If T implements IEquatable<T> return a GenericEqualityComparer<T> if (typeof(IEquatable<T>).IsAssignableFrom(t)) { return (EqualityComparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericEqualityComparer<int>), t); } // If T is a Nullable<U> where U implements IEquatable<U> return a NullableEqualityComparer<U> if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) { RuntimeType u = (RuntimeType)t.GetGenericArguments()[0]; if (typeof(IEquatable<>).MakeGenericType(u).IsAssignableFrom(u)) { return (EqualityComparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(NullableEqualityComparer<int>), u); } } // See the METHOD__JIT_HELPERS__UNSAFE_ENUM_CAST and METHOD__JIT_HELPERS__UNSAFE_ENUM_CAST_LONG cases in getILIntrinsicImplementation if (t.IsEnum) { TypeCode underlyingTypeCode = Type.GetTypeCode(Enum.GetUnderlyingType(t)); // Depending on the enum type, we need to special case the comparers so that we avoid boxing // Note: We have different comparers for Short and SByte because for those types we need to make sure we call GetHashCode on the actual underlying type as the // implementation of GetHashCode is more complex than for the other types. switch (underlyingTypeCode) { case TypeCode.Int16: // short return (EqualityComparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(ShortEnumEqualityComparer<short>), t); case TypeCode.SByte: return (EqualityComparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(SByteEnumEqualityComparer<sbyte>), t); case TypeCode.Int32: case TypeCode.UInt32: case TypeCode.Byte: case TypeCode.UInt16: //ushort return (EqualityComparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(EnumEqualityComparer<int>), t); case TypeCode.Int64: case TypeCode.UInt64: return (EqualityComparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(LongEnumEqualityComparer<long>), t); } } // Otherwise return an ObjectEqualityComparer<T> return new ObjectEqualityComparer<T>(); } [Pure] public abstract bool Equals(T x, T y); [Pure] public abstract int GetHashCode(T obj); internal virtual int IndexOf(T[] array, T value, int startIndex, int count) { int endIndex = startIndex + count; for (int i = startIndex; i < endIndex; i++) { if (Equals(array[i], value)) return i; } return -1; } internal virtual int LastIndexOf(T[] array, T value, int startIndex, int count) { int endIndex = startIndex - count + 1; for (int i = startIndex; i >= endIndex; i--) { if (Equals(array[i], value)) return i; } return -1; } int IEqualityComparer.GetHashCode(object obj) { if (obj == null) return 0; if (obj is T) return GetHashCode((T)obj); ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArgumentForComparison); return 0; } bool IEqualityComparer.Equals(object x, object y) { if (x == y) return true; if (x == null || y == null) return false; if ((x is T) && (y is T)) return Equals((T)x, (T)y); ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArgumentForComparison); return false; } } // The methods in this class look identical to the inherited methods, but the calls // to Equal bind to IEquatable<T>.Equals(T) instead of Object.Equals(Object) [Serializable] internal class GenericEqualityComparer<T>: EqualityComparer<T> where T: IEquatable<T> { [Pure] public override bool Equals(T x, T y) { if (x != null) { if (y != null) return x.Equals(y); return false; } if (y != null) return false; return true; } [Pure] public override int GetHashCode(T obj) { if (obj == null) return 0; return obj.GetHashCode(); } internal override int IndexOf(T[] array, T value, int startIndex, int count) { int endIndex = startIndex + count; if (value == null) { for (int i = startIndex; i < endIndex; i++) { if (array[i] == null) return i; } } else { for (int i = startIndex; i < endIndex; i++) { if (array[i] != null && array[i].Equals(value)) return i; } } return -1; } internal override int LastIndexOf(T[] array, T value, int startIndex, int count) { int endIndex = startIndex - count + 1; if (value == null) { for (int i = startIndex; i >= endIndex; i--) { if (array[i] == null) return i; } } else { for (int i = startIndex; i >= endIndex; i--) { if (array[i] != null && array[i].Equals(value)) return i; } } return -1; } // Equals method for the comparer itself. public override bool Equals(Object obj){ GenericEqualityComparer<T> comparer = obj as GenericEqualityComparer<T>; return comparer != null; } public override int GetHashCode() { return this.GetType().Name.GetHashCode(); } }
看到一个有意思的案例:
public class Entity { public int ID { get; set; } public string Name { get; set; } } IDictionary<Entity, string> dic = new Dictionary<Entity, string>(); dic.Add(new Entity { ID = 1, Name = "小明" }, "C++"); dic.Add(new Entity { ID = 2, Name = "小王" }, "VB"); Entity findkey = new Entity { ID = 2, Name = "小王" }; if (dic.ContainsKey(findkey)) { Console.WriteLine(dic[findkey]); }
最终输出的结果是空。
原因是使用默认的比较器,即使两个类的属性都是相同的,生成的HashCode也是不一样的,所以使用ContainsKey()得出的结果是不一样的。
看下ContainsKey的实现源码:
public bool ContainsKey(TKey key) { return FindEntry(key) >= 0; }
private int FindEntry(TKey key) { if( key == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); } if (buckets != null) { int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; for (int i = buckets[hashCode % buckets.Length]; i >= 0; i = entries[i].next) { if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) return i; } } return -1; }
可以看到这里只比较了hashcode和对应的key。如果想要实现上述效果。可以自定义一个comparer:
public class MyComparer : IEqualityComparer<Entity> { public bool Equals(Entity x, Entity y) { if (x.ID == y.ID && x.Name.Equals(y.Name)) { return true; } return false; } //通过id的hasCode来判断是否相等。 public int GetHashCode(Entity obj) { return obj.ID.GetHashCode(); } }
实例化dictionary时传入:
MyComparer myComparer = new MyComparer(); IDictionary<Entity, string> dic = new Dictionary<Entity, string>(myComparer);
再执行上面的代码就可以获取到想要的结果。
比较过程,是把obj.ID的hashcode先存入。调用Equals方法。