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

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

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

None.gif object  c  =  MakeObject();
None.gif
None.gifMyClass c1 
=  c  as  MyClass;
None.gifc1.MagicMethod();
None.gif
None.gifMyOtherClass c2 
=  c  as  MyOtherClass;
None.gifc2.MagicMethod();

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

None.gif      public   class  MyClass
ExpandedBlockStart.gifContractedBlock.gif    
dot.gif {
InBlock.gif        
public void MagicMethod()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
ExpandedSubBlockEnd.gif        }

ExpandedBlockEnd.gif    }

None.gif
None.gif    
public   class  MyOtherClass : MyClass
ExpandedBlockStart.gifContractedBlock.gif    
dot.gif {
InBlock.gif        
public new void MagicMethod()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
ExpandedSubBlockEnd.gif        }

ExpandedBlockEnd.gif    }

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

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

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

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

None.gif      public   class  MyWidget : BaseWidget
ExpandedBlockStart.gifContractedBlock.gif    
dot.gif {
InBlock.gif        
public void DoWidgetThings()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
ExpandedSubBlockEnd.gif        }

ExpandedBlockEnd.gif    }

      我们完成了这个类并交付用户使用。然后我们发现BaseWidget公司发布了一个新版本的BaseWidget类。对于新特性的热衷让我们立即尝试使用新的基类来构建MyWidget类。但是却失败了,因为我们发现BaseWidget中添加了他们自己的DoWidgetThings方法。

None.gif      public   class  BaseWidget
ExpandedBlockStart.gifContractedBlock.gif    
dot.gif {
InBlock.gif        
public void DoWidgetThings()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
ExpandedSubBlockEnd.gif        }

ExpandedBlockEnd.gif    }

      问题出现了。我们的新基类偷偷的提供了一个和我们类型相同名称的方法。我们有两种方法可以解决这个问题。一是更改我们类中DoWidgetThings方法的名称:

None.gif      public   class  MyWidget : BaseWidget
ExpandedBlockStart.gifContractedBlock.gif    
dot.gif {
InBlock.gif        
public void DoMyWidgetThings()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
ExpandedSubBlockEnd.gif        }

ExpandedBlockEnd.gif    }

      二是使用new修饰符:

None.gif      public   class  MyWidget : BaseWidget
ExpandedBlockStart.gifContractedBlock.gif    
dot.gif {
InBlock.gif        
public new void DoWidgetThings()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
ExpandedSubBlockEnd.gif        }

ExpandedBlockEnd.gif    }

      如果能获得所有所用MyWidget类的客户程序代码,那我们应当选择修改名称的方法。但是如果我们已经发布了MyWidget类,这种修改就会强迫我们所有的用户修改其代码。如果发布范围较大的话,这种修改付出的代价太高。new操作符可以帮助我们处理这个问题。所有的客户端都可以不做任何更改的继续使用DoWidgetThings()方法。没有用户会使用BaseWidget.DoWidgetThings()因为他们不知道基类中有这样一个方法。new操作符在升级基类版本时隐藏了命名冲突的成员。

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

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

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

      回到目录
 

转载于:https://www.cnblogs.com/aiyagaze/archive/2007/04/22/722274.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值