本篇主要着眼于C#中运算符的重载,以及implicit conversion和explicit conversion运算符的实现,为尽可能直观地编写程序提供方便。
- C#中,你不能通过重载改变运算符的运算顺序和优先级;不能改变运算符的操作数个数;不能自定义新的运算符;不能改变运算符对内建类型的处理方法;不能重载dot运算符;应该使用indexer来模拟[]运算符,使用property来模拟=运算符,使用delegate来模拟函数调用。
- 使用operator关键字来进行运算符重载,重载的时候要定义运算符的返回值和参数;所有的运算符都必须是public static的,不能使用virtual/abstract/override/sealed修饰;二元运算符有两个明确的参数,一元运算符有一个明确的参数。
struct Hour
{
public Hour(int initialValue)
{
this.value = initialValue;
}
public static Hour operator+ (Hour lhs, Hour rhs)
{
return new Hour(lhs.value + rhs.value);
}
...
private int value;
}
- 当你使用重载的运算符时,编译器自动将其转换为对用户定义的运算符的调用。
Hour Example(Hour a, Hour b)
{
return a + b;
}
变为:
Hour Example(Hour a, Hour b)
{
return Hour.operator+(a,b); // pseudocode
}
- 在定义运算符的时候,必须至少有一个参数是the containing type(包含你想调用的运算符的类)。例如上面的例子中,至少有一个参数必须是Hour类。
- 你可以多次重载一个运算符,来处理不同的运算方式。
struct Hour
{
public Hour(int initialValue)
{
this.value = initialValue;
}
...
public static Hour operator+ (Hour lhs, Hour rhs)
{
return new Hour(lhs.value + rhs.value);
}
//下面两个运算符的重载方式是对称的,用于使用户可以自由选择左右操作数的顺序
//在C++中你只需要提供一个,编译器会自动Swap
public static Hour operator+ (Hour lhs, int rhs)
{
return lhs + new Hour(rhs);
}
public static Hour operator+ (int lhs, Hour rhs)
{
return new Hour(lhs) + rhs;
}
...
private int value;
}
- 如果你实现了+运算符,最好也实现一个使用同样参数的Add方法,以便符合Common Language Specification (CLS)的要求。
- C#不允许你声明一个用户定义的=运算符,但是+=等组合运算符会自动转换成+等二元运算符进行操作。
- C#允许你声明自己的++和—运算符,并且可以用于前缀和后缀形式,这两个运算符的返回值必须和参数类型一致。
struct Hour
{
...
public static Hour operator++ (Hour arg)
{
arg.value++;
return arg;
}
...
private int value;
}
- 如果你在class中定义了重载++/--运算符,要注意因为赋值语句实际上是增加了一个对现有object的引用,所以对其中任何一个的修改都会影响到另外一个,所以上述struct中++的实现方式会出现错误如下,下面是postfix=now++的内部过程描述:
Hour now = new Hour(9);
Hour postfix = now;
//下面一句对now的更新也会影响到postfix,出现了错误
now = Hour.operator++(now); // pseudocode, not valid C#
- 正确的class中的实现如下,但是这会带来内存使用和回收的很大负担,建议尽量不要在class中重载运算符:
class Hour
{
public Hour(int initialValue)
{
this.value = initialValue;
}
...
public static Hour operator++(Hour arg)
{
return new Hour(arg.value + 1);
}
...
private int value;
}
- 某些运算符的功能是对称的(例如>和<,<=和>=),这时如果你定义了其中的一个,C#编译器会强制你定义另外一个。
- 如果你定义了==和!=运算符,你最好重载从System.Object继承的Equals和GetHashCode方法。
- 内建的数据类型包含某些隐含的转换(implicit conversion),这些转换永远不会生成exceptionclass Example,隐含转换又被称为widening conversion,因为结果类型的取值范围总是比源类型大(例如int转换成double)。
public static void MyDoubleMethod(double parameter)
{
...
}
Example.MyDoubleMethod(42); // implicit int to double conversion
- 但是,double不能隐含转换为int,必须明确调用cast,Explicit conversion又被称为narrowing conversion,可以生成OverflowException.。
class Example
{
public static void MyIntMethod(int parameter)
{
...
}
}
...
Example.MyIntMethod(42.0); // compile-time error
Example.MyIntMethod((int)42.0);
- 你可以定义自己的转换运算符,语法和重载运算符类似,也必须是public static的;要被转换的类型定义为参数,要转换成的类型定义为运算符名,没有返回值;你必须说明这个转换运算符是implicit还是explicit的。
struct Hour
{
...
public static implicit operator int (Hour from)
{
return this.value;
}
private int value;
}
class Example
{
public static void Method(int parameter) { ... }
public static void Main()
{
Hour lunch = new Hour(12);
Example.MyOtherMethod(lunch); // implicit Hour to int conversion
//如果转换运算符是explicit的,就必须是Example.MyOtherMethod((int)lunch);
}
}
- 转换运算符可以作为解决对称运算符问题的一种方法,例如前面例子中Hour类的三个版本的+运算符(Hour + Hour, Hour + int, and int + Hour)可以使用一个运算符(Hour+Hour)配合一个隐含的int to Hour转换来完成:
struct Hour
{
public Hour(int initialValue)
{
this.value = initialValue;
}
public static Hour operator+(Hour lhs, Hour rhs)
{
return new Hour(lhs.value + rhs.value);
}
public static implicit operator Hour (int from)
{
return new Hour (from);
}
...
private int value;
}
void Example(Hour a, int b)
{
Hour eg1 = a + b; // b converted to an Hour
Hour eg2 = b + a; // b converted to an Hour
}
(待续,下一篇将主要描述C#中Enumable Object的实现和作用)