Effective C# Item 28: Avoid Conversion Operators

      转换操作为我们提供了类之间的一种可置换的关系。这意味着一个类的对象可以被其它类的对象替代。这样的好处在于:一个派生类的对象可以被其基类的对象代替。我们可以参考下图结构。我们创建一个Shape基类和三个派生类:Circle,Ellipse,Square。任何情况下都可以用Circle来替代Shape,因为Circle是一个特殊的Shape,这是多态的表现。就像在.Net中,任何对象都可以被System.Object型的对象替代,它是所有类型的基类。同样,我们自己创建的类的对象也可能被实现同样接口的类、实现同样基类接口的类或者基类的对象替代。



      如果我们需要为类定义转换操作,就说明它可能会被其它的目标类型所替换。这种替换经常会引发一些潜在的错误,往往是因为两个类型之间的转换并不完美造成的。另外,在效率方面,修改目标类型的操作效率往往较低,特别是当转换产生临时对象的时候。而且,转换操作的规则基于编译期的类型对象发挥作用,而不是运行期。用户可能会需要使用多种强制类型转换来实现这种替代关系,这可能会造成代码难以维护。

      如果确实需要将一个类型转换为另一个目标类型,我们应当通过构造函数来实现。这样会使我们创建新对象的操作更加清晰。使用隐式转换往往会造成一些难以发现的错误。假设我们有如图中的类结构。从基类中派生出三个子类。我们知道每个圆都是一个椭圆,另外一些特殊的椭圆是圆。虽然圆和椭圆之间是有关系的,但是我们还是将结构设计成这样,这是因为我们不希望在结构中有非抽象的叶子类(否则这里的结构应该是hape->Ellipse->Circle)。由于每个圆都是椭圆,那么我们可以添加一个由圆创建一个新的椭圆的转换:

    class Circle : Shape
    
{
        
private PointF _center;
        
private float _radius;

        
public Circle(PointF c, float r)
        
{
            _center 
= c;
            _radius 
= r;
        }


        
public Circle()
            : 
this(PointF.Empty, 0)
        
{
        }


        static public implicit operator Ellipse(Circle c)
        
{
            
return new Ellipse(c._center, c._center, c._radius, c._radius);
        }

    }

      现在我们就能在任何可以使用Ellipse的地方使用Circle了,这种转换是自动发生的:

public double ComputeArea(Ellipse e)
{
    
//计算面积
}


Circle c 
= new Circle(new PointF(3.0f3.0f), 5.0f);
ComputeArea(c);

      上面的例子展示了一个圆是如何转换为一个椭圆的。ComputeArea函数也可以在转换后正常运行。但这只是运气好罢了,我们看下面的这个函数:

public double Flatten(Ellipse e)
{
    e.R1 
/= 2;
    e.R2 
*= 2;
}


Circle c 
= new Circle(new PointF(3.0f3.0f), 5.0f);
Flatten(c);

      这时程序就不能发挥作用了。由于Flatten()方法使用椭圆做为参数,编译器要求我们必须提供一个将圆变为椭圆的转换。这个转换可以在我们调用时为Flatten()函数提供一个椭圆类型的临时对象作为参数。这个临时对象将被Flatten()函数修改,然后立刻被销毁。那些我们期望的操作并没有发生在原来的Circle c上,而是发生在了临时的对象上。

      既然隐式转换不行,我们继续尝试使用显式的强制类型转换:

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

      这样并没有解决根本问题。我们还是创建了临时对象。Circle c不会发生任何变化。接下来,我们继续尝试通过Ellipse的构造函数来达到转换的目的:

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

      大部分程序员一眼就能看出上面的代码不会达到目的。在Flatten()函数中进行的所有修改还是丢失了。我们可以这样修补它的错误:

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

      变量e就是修改过的椭圆了。通过使用构造函数来取代转换,使得对象的创建过程更加清晰。

      通过转换操作返回的对象属性可能会丧失其原有的某些行为,这会为我们造成另一些麻烦。通过转换操作,一些客户程序可能会访问到对象的内部成员。这一点是我们需要避免的。

      转换操作可能会在编程时为我们带来一些问题。当用户期望使用其他的类型来代替我们原有类型时,我们应当清楚这种转换产生的是一个很快就会被销毁的临时对象。由于这种转换的代码时编译器自动生成的,我们很难发现其中隐藏的bug。因此在条件允许的情况下我们应当尽量避免转换操作。

      译自   Effective C#:50 Specific Ways to Improve Your C#                      Bill Wagner著

      回到目录
 

posted on 2007-04-14 14:57  aiya  阅读(976)  评论(2编辑  收藏  举报