操作符重载
有的编程语言允许一个类型定义操作符应该如何操作类型的实例,比如string类型和int类型都重载了(==)和(+)等操作符,当编译器发现两个int类型的实例使用+操作符的时候,编译器会生成把两个整数加到一起的代码。
当编译器发现两个string类型的实例使用+操作符的时候,编译器会生成把两个字符串连接到一起的代码。那么编译器怎么就会知道这样做呢?如何进行操作符重载呢?
下面C#代码展示了一个类中如何进行操作符重载:
namespace DoNet.Seven.ConsoleApplicationTest { class Program { static void Main(string[] args) { rational r1 = new rational(10); rational r2 = new rational(5); rational r3= r1 + r2; Console.WriteLine(r3.Value); Console.ReadKey(); } } //有理数 public sealed class rational { private int _value = 0; public int Value { get { return _value; } set { _value = value; } } public rational(int value) { this._value = value; } public rational() { } public static rational operator+(rational num1,rational num2) { rational result = new rational(num1.Value+num2.Value); return result; } } }
运行代码输入结果是15
用IL工具看下编译器生成的代码如下:
1、首先CLR规范要求操作符重载方法必须是public和static方法。另外,C#编译器要求操作符重载方法至少有一个参数的类型与当前定义的这个方法的类型相同。之所以这样做,
是为了是编译器能在合理的时间内找到要绑定的操作符方法。
2、编程语言的编译器看到源码中出现一个+操作符时,会检查是否有一个操作数的类型定义了一个名为op_Addtion的specialname方法,而且该方法的参数兼容于操作数的类型,
如果存在这样的一个方法,编译器就生成调用它的代码。如果不存在这样的一个方法,就生成一个编译错误。
3、对于其它操作符编译之后对应的方法如下表所示(左边是一元操作符,右边是二元操作符)
转换操作符
当设计一个类型时应该考虑到和其它类型之间的转换,这个其实很重要,将对我们的编码有很大的好处,就像每个类型都会有的一个方法Tostring()一样,我们定义一个int类型,可以很方便的用tostring()方法把
int转换为string,当然也可以转换为其它类型。就像上面的rational一样,如果能将一个int或者double转换为一个rational,就会很方便,反之亦然。
//有理数 public sealed class rational { private int _value = 0; public int Value { get { return _value; } set { _value = value; } } public rational(int value) { this._value = value; } public rational(double value) { this._value =(int)value; } public rational() { } public int ToInt() { return _value; } public double ToDouble() { return (double)_value; } public static rational operator+(rational num1,rational num2) { rational result = new rational(num1.Value+num2.Value); return result; } }
1、调用这些构造器和方法,开发人员可以很方便的将int和double对象转换成rational对象,这将给编程工作带来很多方便。设计类型时,应该认真考虑类型需要支持的转换构造器和方法。
2、int i=10;long j=i;这样的代码我们经常会看到,那么从int类型到long类型的转换为什么就可以隐士的进行呢?这就涉及到了我们的转换操作符,下面我们也为rational定义几个转换操作符。
namespace DoNet.Seven.ConsoleApplicationTest { class Program { static void Main(string[] args) { int n = 10; rational r1 = n; double d=(double)r1; Console.WriteLine(r1.Value); Console.WriteLine(d.ToString()); Console.ReadKey(); } } //有理数 public sealed class rational { private int _value = 0; public int Value { get { return _value; } set { _value = value; } } public rational(int value) { this._value = value; } public rational(double value) { this._value =(int)value; } public rational() { } public int ToInt() { return _value; } public double ToDouble() { return (double)_value; } public static rational operator+(rational num1,rational num2) { rational result = new rational(num1.Value+num2.Value); return result; } public static implicit operator rational(int value) { return new rational(value); } public static implicit operator rational(double value) { return new rational(value); } public static explicit operator int(rational value) { return value.ToInt(); } public static explicit operator double(rational value) { return value.ToDouble(); } } }
输出的结果是10、10。 我们可以在rational、int、double之间来回转换,是不是觉得挺方便的,在这个过程中,编译器又帮我们做了什么呢?
在C#中,implicit关键字告诉编译器为了生成代码来调用方法,不需要在源代码中进行显示转换,相反,explicit关键字告诉编译器只有在发现了显示转型时,才调用方法。
在implicit或explicit关键字之后,要指定operator关键字告诉编译器该方法是一个转换操作符。在operator之后,指定对象要转换成什么类型。在圆括号内,则指定要从什么类型转换。
C#编译器检测到代码中的转型,并内部生成IL代码来调用rational类型定义的转换操作符方法,如果用反编译器看的话可以发现,转换操作符方法会生成下面这样的代码:
结论
不论是操作符重载还是转换操作符,都是在设计类型是考虑到我们编码方便而设计的,下面我们看下C#中decimal类型中的定义。