.Net 相等性:集合类 Contains 方法详解
2013-04-16 11:34
253 查看
.Net 相等性:集合类 Contains 方法详解(一)
这些方法归根结底都可追溯到以下三个接口上(不考虑非泛型版的):一般集合类的Contains都源自ICollection<T>,字典类的ContainsKey都源自IDictionary<TKey, TValue>。另外System.Linq.Enumerable类(.Net3.0)扩展了IEnumerable<T>接口:
Contains或ContainsKey要将输入值与集合中原有的值进行相等比较,Contains涉及到.Net中的相等性。.Net表示相等有多种方法,先看Object类:
这其中有四个相等的方法:
1 public virtual bool Equals(object obj)
2 public static bool Equals(object objA, object objB)
3 public static bool ReferenceEquals(object objA, object objB)
4 public static bool operator == (object objA, object objB)
第四个是==的运算符重载,系统默认实现。这四个相等性在值类型和引用类型含义不同,要把这四个相等性的问题说明清楚也不是件容易事,大家可以去看下《Effective c#》一书,其中有对此的详细阐述,我就不要详细重复了,简单说一下在引用类型中的含义吧:
1.在引用类型中,ReferenceEquals与==含义相同,都表示引用相等(ReferenceEqual)。
2.Equals(object objA, object objB)内部最终调用Equals(object obj)方法。
3.引用类型不要去重载==运算符,这样会破坏它本来的含义。
总结起来,对引用类型可简化为两个方法,就上面的方法1和方法3,方法3不用操心,它只表示引用相等,不能修改。所以我们只关心方法1,它被标记为virtual,我们可以对它进行重写(override)。
如果定义一个新的类(没有从其它类继承),没有重写Equals(object obj),它将采用一个默认实现,先看该类:
1 class People
2 {
3 public int Id { get; set; }
4 public string Name { get; set; }
5 }
我们写段代码来测试下Equals的默认实现是什么?
1 People p1 = new People { Id = 1, Name = "harry" };
2 People p2 = new People { Id = 1, Name = "harry" };
3
4 bool b1 = p1 == p1;
5 bool b2 = p1.Equals(p1);
6 bool b3 = p1 == p2;
7 bool b4 = p1.Equals(p2);
我们实例化了两个People,具有相同的属性。b1、b2肯定为true,自己和自己比较嘛!再来看b3,这里使用“==”进行比较,前面我们说过“==”是“引用相等”,p1、p2是两个实例,具有不同的引用,所以b3值是false。最后看b4,b4使用了Equals(object obj),也就是前面说的方法一,People类没有重写这个方法,于是就使用了Object类中的默认实现。这个默认实现就是引用相等,即ReferenceEqual。所以b4也是false。
这个默认实现与我们的实际应用含义不相同,两个实例属性全部相同,为什么还不Equal呢。因此对于引用类型,我们应当重写其Equals方法,让它更具有实际意义。下面是一个参考实现(改编自《Effective c#》):
1 public override bool Equals(object obj)
2 {
3 if (obj == null) return false;
4 if (object.ReferenceEquals(this, obj)) return true;
5 //
6 if (this.GetType() != obj.GetType()) return false;
7 //
8 return CompareMembers(obj as People);
9 }
10
11 private bool CompareMembers(People other)
12 {
13 return Id.Equals(other.Id) && Name.Equals(other.Name);
14 }
注意第六行,我们判断两个类的类型是否相同,类型不同我们认定“不相等”。(People类以后可能会有派生类,派生类即使所有属性与父类相同,也认为是不相等,因为类型不同。)
重写Equals后,再来测下上面的b4吧,这次为true了。重写后Equals更具有实际意义,如果非要比较引用相等,用“==”比较即可。
再来看一些与相等性有关的接口:
前两个比较相同,后两个不但可以比较相等还可比较谁大谁小(用于集合排序)。这次只讨论前两个。两个接口的声明如下:
1 public interface IEquatable<T>
2 {
3 bool Equals(T other);
4 }
5 public interface IEqualityComparer<T>
6 {
7 bool Equals(T x, T y);
8 int GetHashCode(T obj);
9 }
IEquatable<T>接口比较简单只有一个方法Equals,我们先给People类实现了,如下:
Code
1 class People : IEquatable<People>
2 {
3 public int Id { get; set; }
4 public string Name { get; set; }
5
6
7 public override bool Equals(object obj)
8 {
9 if (obj == null) return false;
10 if (object.ReferenceEquals(this, obj)) return true;
11 if (this.GetType() != obj.GetType()) return false;
12 return Equals(obj as People);
13 }
14
15 public bool Equals(People other)
16 {
17 if (other == null) return false;
18 if(this == other) return true;
19 return Id.Equals(other.Id) && Name.Equals(other.Name);
20 }
21 }
把刚才的CompareMembers方法改成了Equals。而且是从私有方法变成了公有方法,所以又加上了两行代码(注意还没有对this.Name进行空值判断)。这样一来,前面测试中的计算b4值时调用的不再是Equals(object obj)了,而是调用了Equals(People other),效率会提高一些。
接下来看第二个接口 IEqualityComparer<T>,这个接口用在何处呢?请看下图:
如上这个方法是System.Linq.Enumerabler的一个扩展方法,可以传入一个IComparer<T>作为参数。这个重载 我们直接使用的比较少,大多数情况下我们使用是Collection的Contains<T>(T item)(这个方法扩展后面还会提到)。但IEqualityComparer<T>这个接口很重要,也本文的重点。
现在有一个问题,泛型集合类的Contains方法是调用的两个Equals之中的哪个呢(如People类中,两个Equals分别在7行、15行),又与这些接口什么关系呢?
我们先看使用最频繁的泛型集合类List<T>,来看它的Contains实现:
1 public bool Contains(T item)
2 {
3 if (item == null)
4 {
5 for (int j = 0; j < this._size; j++)
6 if (this._items[j] == null) return true;
7 return false;
8 }
9 EqualityComparer<T> comparer = EqualityComparer<T>.Default;
10 for (int i = 0; i < this._size; i++)
11 if (comparer.Equals(this._items[i], item))return true;
12 return false;
13 }
3~8行,如果传入是item是null,也进行了处理,遍历内部集合_items(其实是个数组,定义为T[] _items),看是否也有空值。
重点在第9行,comparer = EqualityComparer<TSource>.Default(这句代码后面会多次出现)。这里出现了一个EqualityComparer<T>类,和我们前面提到的接口IEqualityComparer<T>很像的,它们是什么关系呢。我把和它和它的派生类都找了出来,连根拔起,如下:
相关文章推荐
- .Net 相等性:集合类 Contains 方法 深入详解
- .net下log4net使用方法详解
- 155_集合_Collection集合的常用方法详解_03_contains
- .NET多线程同步方法详解
- contains与compareDocumentPosition方法详解
- contains与compareDocumentPosition方法详解
- 深入Lumisoft.NET实现邮件发送功能的方法详解
- .net开发:为程式码加上行号的方法详解
- 【.net】asp.net文本编辑器FCKeditor使用方法详解
- .NET多线程同步方法详解
- 详解在.net中读写config文件的各种方法
- 深入Lumisoft.NET实现邮件发送功能的方法详解
- 集合类 Contains 方法 深入详解 与接口的实例
- contains与compareDocumentPosition方法详解
- .NET实用扩展方法详解
- .NET多线程同步方法详解
- .net开发:为程式码加上行号的方法详解
- jQuery使用contains过滤器实现精确匹配方法详解
- .Net整合Json实现REST服务客户端的方法详解
- .NET多线程同步方法详解(一):自由锁(InterLocked)