Effective C# Item 29: Use the new Modifier Only When Base Class Updates Mandate it

      当我们需要重定义一个基类中非虚的成员时,我们可以使用new修饰符,但这并不意味着我们应该这样做。这种重定义会引起行为上的歧义。大部分程序员看到下面的两端代码时都会认为如果这两个类是继承关系的话,它们的行为应该时相同的:

object c = MakeObject();

MyClass c1 
= c as MyClass;
c1.MagicMethod();

MyOtherClass c2 
= c as MyOtherClass;
c2.MagicMethod();

      但是如果通过new进行重定义,那么结果可能并不是这样:

    public class MyClass
    
{
        
public void MagicMethod()
        
{
        }

    }


    
public class MyOtherClass : MyClass
    
{
        
public new void MagicMethod()
        
{
        }

    }

      像这种情况经常会让很多开发人员头疼。如果调用同一个对象的同一个方法,我们期望执行结果是相同的。但是事实上虽然名称相同,但它们调用的函数却是完全不同的,这非常糟糕。它破坏了唯一性。MyOtherClass对象的行为并不是我们期望的。使用new修饰符可以让我们在类中添加一个完全不同的方法,这不同于重写基类中的虚方法。

      非虚的方法都是静态确定的。不论在任何位置的代码,这些方法都调用同样的函数。在运行时不会在其派生类中寻找此方法的不同版本。而虚方法是动态确定的。运行时将通过不同的类型来调用该方法不同的版本。

      我们应当避免使用new来重定义非虚方法。不然我们就需要将基类中的所有方法都声明为虚的。设计者通过定义这些虚方法为类添加了一种行为约定。它的派生类都可以通过这个虚方法来达到自己不同的实现需要。每一条虚方法都定义了其派生类可能需要改变的行为。“默认虚方法”的设计认为基类中所有的方法都为虚,派生类可以修改基类中所有的行为。这往往意味着你并没有认真思考派生类之间行为的分支关系。我们应当多花些时间思考派生类中哪些方法和属性时需要多态性的,仅仅把这些有需要的方法和属性设为虚的。我们不要将这想象为对用户的限制。而是将它看作指导用户自定义行为的入口。

      仅有一种情况下我们应该使用new修饰符:我们更新了一个新版本的基类,而这个基类中有包含和我们原有类型相同名称的方法。我们在下例中创建一个MyWidget类,使用其他类库定义的BaseWidget类做为基类:

    public class MyWidget : BaseWidget
    
{
        
public void DoWidgetThings()
        
{
        }

    }

      我们完成了这个类并交付用户使用。然后我们发现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.DoWidgetThings()因为他们不知道基类中有这样一个方法。new操作符在升级基类版本时隐藏了命名冲突的成员。

      当然,在经过一段时间后,有的用户可能会开始需要使用BaseWidget.DoWidgetThings()方法。这样我们又回到了原来的问题:两个方法的名称相同但是行为不同。考虑到使用new修饰符会产生长期的语义分歧,因此虽然更改方法名称会比较麻烦,但是相比之下这种处理方式往往更加得当。

      在使用new修饰符时必须小心。不恰当的使用会造成方法调用的歧义。只有当升级基类发生命名冲突时才可以使用它来进行特殊处理。而且即便如此也应当谨慎使用。不要在其他情况下使用new修饰符。

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

      回到目录
 

posted on 2007-04-22 11:40  aiya  阅读(855)  评论(0编辑  收藏  举报