==操作符因为语法简洁而备受欢迎,但它本身也存在着局限性,比如继承或泛型问题。下面让我们依次来看看吧。

1、==和继承性问题

  关于==运算符在继承时存在的问题,我们以String类型为例进行说明。

 static void Main(string[] args)
{
string str = "hello";
string str1 = string.Copy(str); Console.WriteLine(ReferenceEquals(str, str1));
Console.WriteLine(str.Equals(str1));
Console.WriteLine(str == str1);
Console.WriteLine(object.Equals(str, str1));
Console.Read();
}

  运行上面代码,依次产生:False、True、True、True。该结果很容易解释,除了ReferenceEquals方法进行引用比较之外,其他三种比较方法均是值比较。

  现在让我们稍微修改一下上述代码,如下所示:

static void Main(string[] args)
{
Object str = "hello";
Object str1 = string.Copy(str); Console.WriteLine(ReferenceEquals(str, str1));
Console.WriteLine(str.Equals(str1));
Console.WriteLine(str == str1);
Console.WriteLine(object.Equals(str, str1));
Console.Read();
}

  再次运行上面代码,结果为:False、True、False、True。和上面的结果进行比较,可以发现,只有==操作符的结果发生了改变,这是为什么呢?

  原因就在于==相当于一个静态方法,而静态方法不可能是virtual的,本例中当用==进行比较时,比较的是两个Object类型的变量,尽管我们知道str和str1实际是String类型的,但编译器并没有意识到这一点。我们应该牢记的一点就是:对于非虚方法的调用而言,具体调用哪个实现是在编译时期就已经做出决定了。具体到我们的例子,就是说,我们声明了Object类型的两个变量str和str1,那么编译器就会生成比较Object类型的代码。而Object类中是没有==操作符的重载版本实现的,因此,==将进行引用相等性比较,因str和str1是两个不同的实例对象,故返回False。

  由于在面临继承时的无能为力,故此是不应选择==,而应使用Equals方法进行判等。接下来看一下Equals方法是如何解决这个问题的。

  显然,当使用Equals方法进行判等测试时,无论调用的是str.Equals还是object.Equals方法,最终调用的都是String类型的override版本实现,故总能计算出我们预期的结果。因此,在存在继承问题时,应使用Equals方法进行判等,而不是==操作符。

  最后,从本例中可以看到,当我们将==操作符的操作数强转到Object类型时,它将进行引用相等性测试,总是和ReferenceEquals的结果保持一致,因此,一些开发者就使用这种方式来比较引用相等性,但这样做的一个缺点在于:其他的开发者在读到这样的代码片段时可能产生疑惑。因此在比较引用相等性时最好总是使用ReferenceEquals方法。

2、==和泛型问题

  ==的另一个缺陷就是不能和泛型很好地工作在一起。考虑下面的代码:

static void Equals<T>(T a, T b)
{
Console.WriteLine(a == b);
}

  上面代码的逻辑很简单,就是使用==比较两个T类型的对象。但是编译上述代码将报错:

  之所以报上面的错误,是因为T可能代表任意类型,包括基元类型、值类型和引用类型。无法确定传递的类型是否实现了==操作符重载。

  在C#中,对于泛型类型,我们无法施加这样的约束,即强制要求传入的泛型类型T实现==的重载。

  现在,我们能够构建代码,仅有的一个问题就是:Equals被限定在仅能接受引用类型,而不能接受值类型。

  现在,让我们还是以之前的字符串比较为例,

class Program
{
static void Main(string[] args)
{
Object str = "hello";
Object str1 = string.Copy((string)str); Equals(str, str1);
} static void Equals<T>(T a, T b) where T : class
{
Console.WriteLine(a == b);
}
}

  现在,猜测一下上面代码的输出结果,是true还是false ?如果我们回想起String类型定义了==操作符的重载实现,那么很可能猜测上面代码的结果为true,但实际运行结果却显示false,这是为何呢?此时很直观的猜测就是==操作符计算的是引用判等,而非值判等。下面让我们看看究竟发生了什么。

  上面的代码中,尽管通过where T : class语句限定使得编译器知道它能对传入的任何类型应用==操作符进行判等性测试,对应到本例T就是String类型,而且String类型提供了==的重载实现,但编译器并不晓得泛型类型T是否重载了==操作符,因此,它假定T没有重载==。编译器编译代码时假定调用的是Object基类==操作符,因此,==操作符进行引用判等性测试。

  应该始终牢记一点:在对泛型类型T使用==操作符时,编译器不会使用类型T定义的==运算符重载,相反,它会将T视为Object类型,并调用Object基类的==操作符方法。

  接下来让我们看下Equals方法如何解决上面的问题。

static void Equals<t>(T a, T b)
{
Console.WriteLine(object.Equals(a,b));
}

  可以看出,我们移除了泛型方法的class限定语句,因为能在任何类型上调用Object.Equals方法,之所以使用static限定,是为了避免发出调用的实例对象为null,可以看出上面的泛型方法对值类型和引用类型均奏效。

  我们再次运行上面的代码,结果显示True。这是因为Object.Equals方法在运行时将调用它的override版本实现,本例中override版实现的定义位于String类型中,该实现进行值判等性测试。

C#中==操作符存在的缺陷的更多相关文章

  1. 浅析c#中==操作符和equals方法

    在之前的文章中,我们讲到了使用C#中提供的Object类的虚Equals方法来判断Equality,但实际上它还提供了另外一种判断Equality的方法,那就是使用==运算符.许多童鞋也许会想当然的认 ...

  2. C语言中操作符的优先级大全

    C语言中操作符的优先级大全, 当然c++, Objective-C,大部分语言都试用. 下面是来自The C Programming Language 2th的总结. OperatorsAssocia ...

  3. 关于js中for in的缺陷浅析

    关于js中for in的缺陷浅析 http://www.jb51.net/article/44028.htm

  4. 坑:JavaScript 中 操作符“==” 和“===” 的区别

    标题:JavaScript 中 操作符"==" 和"===" 的区别 记录一些很坑的区别: 1. '' == '0' // false 0 == '' // t ...

  5. JavaScript中+操作符的特殊性

    在JavaScript中+操作符有两个作用: (1)加法运算 (2)字符串连接 在使用+操作符进行运算时,当+操作符两边都是数值类型的时候,进行加法运算; 当+操作符两边有任意一边是字符串,则进行字符 ...

  6. Java基础学习总结(67)——Java接口API中使用数组的缺陷

    如果你发现在一个接口使用有如下定义方法: public String[] getParameters(); 那么你应该认真反思.数组不仅仅老式,而且我们有合理的理由避免暴露它们.在这篇文章中,我将试图 ...

  7. MongoDB中“$”操作符表达式汇总

    MongoDB中"$"操作符表达式汇总 查询 比较操作 $eq 语法:{ : { $eq: } } 释义:匹配等于(=)指定值的文档 举例: 查询age = 20的文档: db.p ...

  8. C、C++中“*”操作符和“后++”操作符的优先级

    假设有如下的定义 char carr[] = {"test"}; char cp = carr; 那么表达式 *cp++; 的右值是什么呢? 这个表达式在数组遍历的程序中非常常见, ...

  9. 你所不知道的,Java 中操作符的秘密?

    在 Java 编程的过程中,我们对数据的处理,都是通过操作符来实现的.例如,用于赋值的赋值操作符.用于运算的运算操作符等.用于比较的比较操作符,还包括逻辑操作符.按位操作符.移位操作符.三元操作符等等 ...

随机推荐

  1. Python写一个目录检索器

    前言: 昨天看了Demon哥发的干货,有了次篇博文 干货链接: https://www.soffensive.com/2018/06/exploiting-blind-file-reads-path. ...

  2. probably another instance of uWSGI is running on the same address (127.0.0.1:9090). bind(): Address already in use

    probably another instance of uWSGI is running on the same address (127.0.0.1:9090). bind(): Address ...

  3. Supervisor安装与配置

    Supervisor(http://supervisord.org/)是用Python开发的一个client/server服务,是Linux/Unix系统下的一个进程管理工具,不支持Windows系统 ...

  4. 根据车辆品牌获取品牌所属公司,车标logo,创建年份等基本信息

    接口:http://api.besttool.cn/?c=Car&a=brand 请求方式:post 参数: appid 请联系博主QQ987332767获取,注明车标接口,测试appid: ...

  5. 解题报告-908. Smallest Range I

    题目 : Given an array A of integers, for each integer A[i] we may choose any x with -K <= x <= K ...

  6. Eclipse下生成.dll动态库及.a静态库使用 for Windows [z]

    以后的主要工作就是做库了,将我们的C或者C++写的接口做成库,给客户端使用,因此有必要知道库的使用和制作方法.主要是在Eclipse下搞了搞,公司用的是Carbide,也差不多.库做好了,用SVN已提 ...

  7. [SoapUI]怎样运用Schema通过*.xsd文件来验证response对应的xml文件

    添加Groovy Script脚本对Test Step进行验证 脚本如下(已经运行通过): import javax.xml.XMLConstants import javax.xml.transfo ...

  8. xml数据改动

    public void reXml ( string namepngname ) { XmlDocument doc = new XmlDocument(); doc.Load(_xmlpath); ...

  9. 【转】C中的静态存储区和动态存储区

    一.内存基本构成    可编程内存在基本上分为这样的几大部分:静态存储区.堆区和栈区.他们的功能不同,对他们使用方式也就不同.    静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个 ...

  10. curl:get,post 以及SoapClien访问webservice数据

    一.curl get模式 public function close_order(){ $url="http://192.168.2.50:7777/U8API.asmx?op=Insert ...