随笔 - 13, 文章 - 0, 评论 - 15, 引用 - 1
数据加载中……

.NET执行==运算符时的转换处理

这个问题我最早发在CSDN论坛上,经过很多网友的热心帮助,最后我得出了一个结论。对不对我不知道,但是我有相当的自信是这样的,希望有人能告诉我吧。
例子代码如下:
using System;
using System.Collections;


struct Digit
{
    
byte value;

    
public Digit(byte value)
    {
        
if (value > 9throw new ArgumentException();
        
this.value = value;
    }

    
public static implicit operator byte(Digit d)
    {
        Console.WriteLine(
"Conversion occured");
        
return (byte)((int)d.value + 1);
    }
}

class app
{
    
static void Main()
    {
        Digit d 
= new Digit(5);
        
byte b = d;
        Console.WriteLine(b 
== d);
    }
}
运行这段代码,结果是什么呢?
Conversion occured
Conversion occured
True
刚开始我百思不得其解,因为d的值是5,而b的值是6,怎么会相等呢?后来在网友的帮助下,我得出了下面的看法:
首先,==运算符两边的两个操作数都是值类型,所以==运算符只比较其值是否相等即可。
然后,也是最重要的一点就是,==运算符两边的操作数的类型必须要一致,并且这个类型要重载了==运算符的实现。很显然,我自定义的Digit结构并没有重载==的实现。而据我推测,byte类型作为c#的基元类型,是实现了==运算符的。因此,在这里,CLR在执行的时候会将Digit转换到byte类型再比较,而转换的结果比转换前的值要大1,所以两者都是6了(注意转换后d的值还是5,但是==比较的是b和将d转换后的返回值)。结果就是True了。这也可以解释为什么会出现两个“Conversion occured”了。因为进行了两次隐式转换,为变量b赋值时进行了一次,==比较的时候又进行了一次。通过单步执行可以更清楚地看到这个过程。

为了更加深入研究这个例子,并且证明我前面的猜测:==运算符两边的操作数的类型必须要一致,并且这个类型要重载了==运算符的实现。我把代码做了一点小的修改,Main方法替换为下面的代码:
        Digit d = new Digit(5);
        Digit d2 
= new Digit(6);
        Console.WriteLine(d 
== d2);
按照我的猜测,虽然==运算符两边都是Digit,而Digit又没有重载==运算符,但这时并不是不能比较,因为我们在Digit结构中定义了从Digit到byte的隐式转换。所以在执行到==的时候,会分别将d和d2转换到byte类型,再进行比较,即比较6和7了,最后结果当然就是:
Conversion occured
Conversion occured
False
如果我们把Digit定义中到byte的隐式转换注释掉,那么c#编译器就会报如下的错误:
Operator '==' cannot be applied to operands of type 'Digit' and 'Digit'
因为Digit没有重载==运算符,编译器又找不到存在的任何转换(包括隐式的和显式的),所以就会报错。

这个例子告诉我们,如果我们自己定义了结构,而且需要对该结构类型的对象进行==运算,那么我们就要自己定义==运算符,否则结果就可能不是我们预期的那样了:
using System;
using System.Collections;


struct Digit
{
    
byte value;

    
public Digit(byte value)
    {
        
if (value > 9throw new ArgumentException();
        
this.value = value;
    }

    
public static implicit operator byte(Digit d)
    {
        Console.WriteLine(
"Conversion occured");
        
return (byte)((int)d.value + 1);
    }

    
public static bool operator ==(byte b, Digit d)
    {
        
return b == d.value;
    }
    
public static bool operator !=(byte b, Digit d)
    {
        
return !(b == d);
    }
}

class app
{
    
static void Main()
    {
        Digit d 
= new Digit(5);
        
byte b = d;
        Console.WriteLine(b 
== d);
    }
}
这样,由于我们实现了在byte和Digit类型之间的==运算符,所以就不会像前面那样进行类型转换了,结果也当然是我们预期的那样了:
Conversion occured
False

posted on 2005-07-12 16:24 buaaytt 阅读(1462) 评论(8)  编辑 收藏 所属分类: .NET

评论

#1楼    回复  引用    

这种基础问题要多看书。

#2楼    回复  引用  查看    

有时觉得直接用Equals方法还要比==明显些
2005-07-12 18:10 | linkcd      

#3楼    回复  引用  查看    

不是問題的問題.
2005-07-13 08:58 | James      

#4楼    回复  引用    

@linkcd,你可以用Equals试试,看看结果是不是和==一样的,实际上两者是不同的。
2005-07-13 09:33 | buaaytt [未注册用户]

#5楼    回复  引用    

唉,让人见笑了,还是把这些争吵删了吧。问题讨论演变成人身攻击就不好了
2005-07-13 10:33 | buaaytt [未注册用户]

#6楼 [楼主]   回复  引用  查看    

@linkcd,事实上,如果是用b.Equals(d),那么结果是true,并且有两次类型转换。因为byte结构的定义中提供了一个Equals(byte obj)方法,所以这里会把d转换成byte类型(这里有个问题,见后面)。而如果用d.Equals(b),结果是false,并且只有一次类型转换。这个我想是因为Digit没有重写Equals方法,所以就调用的是ValueType的Equals方法。因此不会把d转换到byte类型,所以结果就是false了。
但是这里我还有个问题,当使用b.Equals(d)的时候,Byte结构定义了两个Equals方法,一个是public bool Equals(byte obj),另一个当然就是public override bool Equals(object obj)了。那么在这里CLR怎么决定该调用哪个重载版本呢?
2005-07-13 10:53 | buaaytt      

#7楼    回复  引用  查看    

@buaaytt:
按照最接近原则处理。如果是byte或者其派生类(当然,byte没法派生,但是其他类型是可以的),那就使用byte版本,否则用object版本。
2005-07-13 15:29 | sumtec      

#8楼    回复  引用    

◎sumtec,我猜测意思也是这样。但是你的说法好像不是很准确吧,因为Digit并不是从byte派生的,而只是定义了到byte的转换
2005-07-13 16:43 | buaaytt [未注册用户]

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
"五向定位"职业成长路线公开课(上海、南京、大连)
Google站内搜索


相关链接: