Effective C#之Item 28: Avoid Conversion Operators

  rel="File-List" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_filelist.xml"> rel="Edit-Time-Data" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_editdata.mso"> rel="themeData" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_themedata.thmx"> rel="colorSchemeMapping" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_colorschememapping.xml">

Item 28: Avoid Conversion Operators

避免转换操作符

Conversion operators introduce a kind of substitutability between classes. Substitutability means that one class can be substituted for another. This can be a benefit: An object of a derived class can be substituted for an object of its base class, as in the classic example of the shape hierarchy. You create a Shape base class and derive a variety of customizations: Rectangle, Ellipse, Circle, and so on. You can substitute a Circle anywhere a Shape is expected. That's using polymorphism for substitutability. It works because a circle is a specific type of shape. When you create a class, certain conversions are allowed automatically. Any object can be substituted for an instance of System.Object, the root of the .NET class hierarchy. In the same fashion, any object of a class that you create will be substituted implicitly for an interface that it implements, any of its base interfaces, or any of its base classes. The language also supports a variety of numeric conversions.

转换操作符在类之间引入了一种可替换性。可替换性意味着一个类的实例可以由另一个类的实例替换。这有一个好处:派生类的对象可以被替换为基类的对象,就像经典的shape层次的例子一样。创建Shape基类,派生出一系列的变种:RectangleEllipseCircle等等。你可以在任何期望Shape的地方使用Circle来替换。这是为替换使用了多态。因为一个圆是形状的一种,所以这行得通。当你创建一个类的时候,一些转换是自动被允许的。任何对象都可以替换System.Object(.Net类层次的根)的实例。同样,任何你创建的类的对象都可以隐式的替换下面的东西:它实现的接口、它的基类的任何接口、它的任何基类。C#语言也支持一系列的数值转换。

When you define a conversion operator for your type, you tell the compiler that your type may be substituted for the target type. These substitutions often result in subtle errors because your type probably isn't a perfect substitute for the target type. Side effects that modify the state of the target type won't have the same effect on your type. Worse, if your conversion operator returns a temporary object, the side effects will modify the temporary object and be lost forever to the garbage collector. Finally, the rules for invoking conversion operators are based on the compile-time type of an object, not the runtime type of an object. Users of your type might need to perform multiple casts to invoke the conversion operators, a practice that leads to unmaintainable code.

当你为自己的类型定义一个转换的时候,就是告诉编译器,你的类型可以替换一个目标类型。因为你的类型可能不是目标类型的完美替代品,所以经常导致细微的错误。改变了目标类型的副作用不会修改你的类型。更糟糕的是,如果你的转换操作符返回一个临时对象,副作用将会修改这个临时对象,将会永远遗失在垃圾收集器里。最后,调用转换操作符的规则,是基于一个对象的编译时类型的,而不是它的运行时类型。你的类型的用户可能需要执行多步转换来调用转换操作符,从经验上讲,这会导致不可维护的代码。

If you want to convert another type into your type, use a constructor. This more clearly reflects the action of creating a new object. Conversion operators can introduce hard-to-find problems in your code. Suppose that you inherit the code for a library shown in Figure 3.1. Both the Circle class and the Ellipse class are derived from the Shape class. You decide to leave that hierarchy in place because you believe that, although the Circle and Ellipse are related, you don't want to have nonabstract leaf classes in your hierarchy, and several implementation problems occur when you try to derive the Circle class from the Ellipse class. However, you realize that every circle could be an ellipse. In addition, some ellipses could be substituted for circles.

如果你希望将另外的类型转换成你的类型,使用构造器。这可以很明显的反应创建新对象的动作。在你的代码里面,转换操作符可以引发难以发现的错误。假设你继承了在图3.1里面展示的库的代码。CircleEllipse类都是由Shape类派生来的。因为你相信,虽然CircleEllipse是有关系的,但是在你的类继层次内,你不想要非抽象的叶子类,所以你决定使用这个层次。当你尝试从Ellipse类派生Circle类的时候,会发生一些实现上的问题。然而,你意识到每个圆都可以是椭圆。另外,一些椭圆可以用圆来代替。


That leads you to add two conversion operators. Every Circle is an Ellipse, so you add an implicit conversion to create a new Ellipse from a Circle. An implicit conversion operator will be called whenever one type needs to be converted to another type. By contrast, an explicit conversion will be called only when the programmer puts a cast operator in the source code.

这导致你添加2个转换操作符。每个Circle都是一个Ellipse,因此你添加一个隐式的转换来从一个Circle创建一个新的Ellipse。当一个类型需要转换成另外一个类型的时候,隐式的转换操作符会被调用。相比较来说,只有当程序员在代码里面添加强制转换操作符的时候,显式的转换才会被调用。

  1.     public class Circle : Shape
  2.     {
  3.         private PointF center;
  4.         private float radius;
  5.  
  6.         public Circle():this(PointF.Empty, 0)
  7.         {
  8.         }
  9.  
  10.         public Circle(PointF c, float r)
  11.         {
  12.             center = c;
  13.             radius = r;
  14.         }
  15.  
  16.         public override void Draw()
  17.         {
  18.             //...
  19.         }
  20.  
  21.         static public implicit operator Ellipse(Circle c)
  22.         {
  23.             return new Ellipse(c.center, c.center,c.radius, c.radius);
  24.         }
  25.   }

Now that you've got the implicit conversion operator, you can use a Circle anywhere an Ellipse is expected. Furthermore, the conversion happens automatically:

既然你已经有了隐式转换操作符,就可以在任何期望Ellipse的地方使用Circle。进一步说,该转换会自动发生。

  1.     public double ComputeArea( Ellipse e )
  2.     {
  3.     // return the area of the ellipse.
  4.    }
  5.     // call it:
  6.     Circle c = new Circle( new PointF( 3.0f, 0 ), 5.0f );
  7.    ComputeArea(c);

This sample shows what I mean by substitutability: A circle has been substituted for an ellipse. The ComputeArea function works even with the substitution. You got lucky. But examine this function:

该例子展示了我说的替换的意思:一个圆替换了一个椭圆。ComputeArea方法甚至可以工作在替换品上。你走运了,但是看看这个方法:

  1.     public void Flatten(Ellipse e)
  2.     {
  3.         e.R1 /= 2;
  4.         e.R2 *= 2;
  5.     }
  6.     // call it using a circle:
  7.     Circle c = new Circle( new PointF ( 3.0f, 0 ), 5.0f );
  8.     Flatten( c );

This won't work. The Flatten() method takes an ellipse as an argument. The compiler must somehow convert a circle to an ellipse. You've created an implicit conversion that does exactly that. Your conversion gets called, and the Flatten() function receives as its parameter the ellipse created by your implicit conversion. This temporary object is modified by the Flatten() function and immediately becomes garbage. The side effects expected from your Flatten() function occur, but only on a temporary object. The end result is that nothing happens to the circle, c.

这行不通。Flatten()采用椭圆做参数,编译器必须在一定程度上将一个圆转换成椭圆。你已经创建了一个隐式转换来精确的做那件事。你的转换被调用,Flatten()方法接收你的隐式转换创建的椭圆作为参数。这个临时对象由Flatten()方法修改,迅速变成垃圾。Flatten()带来的副作用发生了,但是仅仅发生在临时对象上。最终结果就是对于原来的那个圆,c,什么也没有发生。

Changing the conversion from implicit to explicit only forces users to add a cast to the call:

将转换从隐式改成显式,仅仅强制用户使用添加强制转换符来调用它。

  1.     Circle c = new Circle(new PointF(3.0f, 0), 5.0f);
  2.     Flatten((Ellipse)c);

The original problem remains. You just forced your users to add a cast to cause the problem. You still create a temporary object, flatten the temporary object, and throw it away. The circle, c, is not modified at all. Instead, if you create a constructor to convert the Circle to an Ellipse, the actions are clearer:

原来的问题依然存在。你仅仅是强制你的用户添加强制转换符,同样会引起这个问题。你仍然创建了一个临时对象,对这个临时对象进行flatten操作,然后丢弃它。原来的圆,c,根本就没有被修改。相反,如果你创建一个构造函数来将Circle转换成Ellipse,所做的一切就明显了。

  1.     Circle c = new Circle(new PointF(3.0f, 0), 5.0f);
  2.     Flatten(new Ellipse(c));

Most programmers would see the previous two lines and immediately realize that any modifications to the ellipse passed to Flatten() are lost. They would fix the problem by keeping track of the new object:

多数程序员将看到前面的2行,迅速的意识到,任何对椭圆的修改,在传递给Flatten()的时候都丢失了。他们会通过跟踪新对象来修正这个问题。

  1.     Circle c = new Circle(new PointF(3.0f, 0), 5.0f);
  2.     // Work with the circle.
  3.     // ...
  4.  
  5.     // Convert to an ellipse.
  6.     Ellipse e = new Ellipse(c);
  7.     Flatten(e);

The variable e holds the flattened ellipse. By replacing the conversion operator with a constructor, you have not lost any functionality; you've merely made it clearer when new objects are created. (Veteran C++ programmers should note that C# does not call constructors for implicit or explicit conversions. You create new objects only when you explicitly use the new operator, and at no other time. There is no need for the explicit keyword on constructors in C#.)

变量e保存变平的椭圆。通过使用构造函数替换转换操作,不但没有漏掉任何功能,而且在创建新对象的时候更清晰。(C++老手会注意到C#不会为了显式或者隐式转换调用构造函数。只有当你显式的使用new操作符而不是其他时候,才创建新对象。在C#语言里,对于构造函数没有必要存在explicit关键字。)

Conversion operators that return fields inside your objects will not exhibit this behavior. They have other problems. You've poked a serious hole in the encapsulation of your class. By casting your type to some other object, clients of your class can access an internal variable. That's best avoided for all the reasons discussed in Item 23.

返回你的对象内部字段的转换操作符,将不会展示这个行为,它们有其他的问题,在类的封装性上制造了一个严重的漏洞。通过将你的类型强制转换成其它一些对象,你的类的客户可以访问内部变量。出于在Item23讨论过的原因,这最好要避免。

Conversion operators introduce a form of substitutability that causes problems in your code. You're indicating that, in all cases, users can reasonably expect that another class can be used in place of the one you created. When this substituted object is accessed, you cause clients to work with temporary objects or internal fields in place of the class you created. You then modify temporary objects and discard the results. These subtle bugs are hard to find because the compiler generates code to convert these objects. Avoid conversion operators.

转换操作符引入了替换性,但是在代码里会引发问题。你在表明:可以使用一个类来替换你创建的另一个类,在所有这些情况下,你需要替代性。当被替换的对象可以被访问时,你会使得客户在临时对象或者内部字段上工作,而不是你创建的类对象本身。你修改临时对象并丢弃结果,这些微小的bug很难被发现,因为编译器会生成代码来对这些对象进行转换。因此,避免转换操作符。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值