Effective C# 只有当新版基类导致问题时才考虑使用new修饰符

我们一般在类成员上使用new修饰符,来重新定义继承自基类的非虚成员。我们可以这么做并不意味着我们就应该这么做。重新定义非虚方法会导致含混不清的行为。例如,对于下面的代码,绝大多数开发人员都会不假思索地认为它们的行为是一样的(假设两个类有继承关系):

object c = MakeObject( );

// 通过MyClass引用调用:

MyClass cl 
= c as MyClass;

cl.MagicMethod( );

// 通过MyOtherClass引用调用:

MyOtherClass cl2 
= c as MyOtherClass;

cl2.MagicMethod( );

 

如果使用了new修饰符,情况就不是这样了:

 

public class MyClass

{

public void MagicMethod( )

{

    
// 忽略细节。

}


}


public class MyOtherClass : MyClass

{

// 重新定义MagicMethod。

public new void MagicMethod( )

{

    
// 忽略细节。

}


}

 

这种做法会导致许多含混不清的地方。如果在同样的对象上调用同样的函数,我们期望同样的代码被执行。但事实是,如果我们更改了用来调用函数的引用,函数调用的行为也将有所不同。这种不一致的行为看上去很荒唐。一个MyOtherClass对象,由于对它的引用不同,而有不同的行为。修饰符new并不会将一个非虚方法变为一个虚方法。相反,它会在类中添加一个不同的方法。

        非虚方法为静态绑定。任何引用MyClass.MagicMethod()的源代码调用的都将是该方法。系统不会在运行时寻找派生类中定义的其他版本。另一方面,虚函数使用的是动态绑定。系统会根据对象的运行时类型来选择调用正确的函数。

        避免使用new修饰符来重定义非虚函数,并非意味着我们要将基类中所有的函数都定义为虚函数。当程序库的设计者将一个函数定义为虚函数时,实际上是为类型订立了一项合同:即表明任何派生类都可以更改虚函数的实现。事实上,虚函数集合定义了派生类中所有可能改变的行为。“默认为虚”的设计表明派生类可以更改类的所有行为。这意味着我们没有仔细思考派生类到底会更改哪些部分的行为。我们不应该这么做。相反,我们应该花费时间仔细考虑应该将哪些方法和属性声明为多态成员。我们应该仅将它们声明为虚成员。不要认为这种做法是对类的用户的限制。相反,应该将这种做法当作是在为定制类型行为提供一些入口点。

        仅有一种情况我们需要使用new修饰符,那就是在我们使用新版的基类后,其增添的方法名和子类中现在已经被使用的方法名冲突。因为已经有代码在依赖子类中现有的方法名了,比如可能有其他程序集在使用这样的方法。例如,我们通过继承另外一个程序库中定义的BaseWidget,定义了新的MyWidget类:

 

public class MyWidget : BaseWidget

{

public void DoWidgetThings( )

{

    
// 忽略细节。

}


}

 

假设我们完成了MyWidget之后,已经有客户在使用它。然后我们发现BaseWidget公司又发布了一个新版的BaseWidget。由于对其中的新功能抱有热切的期待,我们立即购买了它,并试图生成新版的MyWidget。可是,生成的时候失败了,原因在于BaseWidget添加了自己的DoWidgetThings方法。

 

public class BaseWidget

{

public void DoWidgetThings()

{

    
// 忽略细节。

}


}

 

这是一个问题,我们的基类悄无声息地在其内引入了一个和子类同名的方法。有两种修正该问题的方法。首先,我们可以更改DoWidgetThings方法的名字:

 

public class MyWidget : BaseWidget

{

public void DoMyWidgetThings( )

{

    
// 忽略细节。

}


}



        或者,我们可以使用new修饰符:

public class MyWidget : BaseWidget

{

public new void DoWidgetThings( )

{

    
// 忽略细节。

}


}

 

 

如果能访问到MyWidget类的所有客户程序代码,我们应该选择更改方法的名字,因为这在长期来讲比较方便。但是,如果我们的MyWidget类发行遍布全世界,那将迫使所有客户做繁多的更改。这就是 new修饰符的用武之地了。我们的客户可以继续使用DoWidgetThings()方法而无需做任何更改。他们也不会调用 BaseWidget.DoWidget- Things(),因为这样的调用在客户代码中不可能存在。修饰符new正是应用于这样的场合:新版的基类增添的成员与子类中先前已经声明的成员发生了冲突。

        当然,随着时间的推移,我们的用户可能也会试图去使用BaseWidget.DoWidget- Things()方法。这时候我们又回到了原来的问题上:两个方法看起来相同,但实际上不同。因此,我们应该考虑new修饰符所带来的长期不良影响。有时候,短期内更改方法名所导致的不方便可能仍然是值得的。

      综上所述,使用new修饰符必须小心。如果不分青红皂白地使用,便会在对象上出现含混不清的方法调用。只有在“新版的基类增添的成员与子类中已存在的成员发生了冲突”这样特殊的情况下,我们才应考虑使用 new修饰符。即使在这种情况下,在使用它之前我们也要慎重考虑。除此之外,我们不应该再在任何其他情况下使用new修饰符。
posted @ 2008-11-02 08:47  瞪着你的小狗  阅读(321)  评论(0编辑  收藏  举报