C# 比较对象的相等性(长文慎入)

理解对象相等的机制对逻辑表达式的编程非常重要, 另外对实现运算符重载和类型强制转换也非常重要。

对象相等的机制有所不同,这取决于比较的是引用类型(类的实例),还是值类型(基本数据类型, 结构 或 枚举的实例)。

本章会单独介绍引用类型的相等性和值类型的相等性。

比较引用类型的相等性

System.Object 定义了3个不同的方法来比较对象的相等性 : ReferenceEquals() 和两个版本的 Equals(). 再加上比较运算符 == ,实际上有4种比较相等性的方法。 这些方法有一些细微的区别, 下面就介绍它们:
 

1. ReferenceEquals() 方法

ReferenceEquals() 是一个静态方法,其测试两个引用是否指向类的同一个实例, 特别是两个引用是否包含内存中的相同地址。作为静态方法,它不能重写, 所以 System.Object 的实现代码保持不变。 如果提供的两个引用指向同一个实例, 则 ReferenceEquals() 总是返回 true; 否则就返回 false。 但是它认为 null 等于 null 。 下面做个试验:

public class TestEquals { }

void Main()
{
    // 测试两个引用不同的对象
    TestEquals testA = new TestEquals();
    TestEquals testB = new TestEquals();
    Console.WriteLine( ReferenceEquals(testA, testB) );


    // 测试两个引用相同的对象
    TestEquals testC = testA;
    Console.WriteLine(ReferenceEquals(testA, testC));


    // 测试两个null的结果
    Console.WriteLine(ReferenceEquals(null, null));
}

// 测试返回结果
false
true
true

2. Equals() 虚方法

Equals() 虚方法在自己的类中重写它,从而按值来比较对象。 在重写 Equals()方法时要注意, 重写的代码不应抛出异常。 同理,这是因为如果抛出异常, 字典类就会出问题, 一些在内部调用这个方法的 .net 基类也可能出问题。

public class TestEquals
{
    public string Name { get; }

    public TestEquals(string name)
    {
        this.Name = name;
    }

    // core
    public override bool Equals(object obj)
    {
        return this.Name == (obj as TestEquals) ?.Name;
    }
}

void Main()
{
    TestEquals testA = new TestEquals("张三");
    TestEquals testB = new TestEquals("李四"); 
    TestEquals testC = new TestEquals("张三");

    // 测试检查相等性
    Console.WriteLine( testA.Equals(testB) );
    Console.WriteLine( testA.Equals(testC) );
}

// 输出结果
false
true

3.静态的Equals()方法

静态版本与虚版本的作用相同, 其区别是静态版本带有两个参数,并对它们进行相等性比较。 这个方法可以处理两个对象中有一个是 null 的情况。 因此,如果一个对象可能是 null ,这个方法就可以抛出异常, 提供额外保护。静态重载版本首先要检查传递给它的引用是否为 null。 如果他们都是null 就返回 true(因为 null 与 null 相等)。 如果只有一个引用是 null, 就返回false。 如果两个引用实际上引用了某对象, 它就调用 Equals 的虚实例版本。 这表示在重写 Equals的实例版本时, 其效果相当于也重写了静态版本。

void Main()
{
    TestEquals testA = new TestEquals("张三");
    TestEquals testB = new TestEquals("李四"); 
    TestEquals testC = new TestEquals("张三");

    // 测试检查相等性
    Console.WriteLine( Equals(testA, testB) );
    Console.WriteLine( Equals(testA, testC) );
}

// 输出结果
false
true

4. 比较运算符 ==

最好将比较运算符看做严格的值比较和严格的引用比较。

void Main()
{
    TestEquals testA = new TestEquals("张三");
    TestEquals testB = new TestEquals("李四");
    TestEquals testC = testA;

    // 测试检查相等性
    Console.WriteLine( testA == testB );
    Console.WriteLine( testA == testC );
}

// 输出结果
false
true

 

比较值类型的相等性

在比较值类型的相等性时, 采用与引用类型相同的规则:ReferenceEquals() 用于比较引用, Equals() 用于比较值, == 可以看做是一个中间项,既可以比较引用又可以比较值。 但最大的区别是值类型需要装箱,才能把它们转换为引用,进而才能对它们执行方法。 

这里有一个特别注意的地方就是 ReferenceEquals() 在应用于值类型时总是返回 false,因为调用这个方法,值类型需要装箱到对象中。造成引用不同,所以会总是返回false

bool b = ReferenceEquals(v, v); // 这里的两个v会单独进行装箱操作,所以他们的引用会不同

出于上述原因, 调用ReferenceEquals() 来比较值类型实际上没有意义。

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值