我们应该什么时候使用函数隐藏(new关键字,不明白的请移步MSDN)?
遵守规则
且看如下一段代码:
public class Base { public virtual void Method() { Console.WriteLine("Base: Method()"); } } public class A : Base { public override void Method() { Console.WriteLine("A: Method()"); } } public class B : Base { public new void Method() { Console.WriteLine("B: Method()"); } } public class TestCase { public static void Main(string[] args) { List<Base> testList = new List<Base>(); testList.Push(new A()); testList.Push(new B()); foreach(Base item in testList) { item.Method(); } } }
可以看到,Base类中将Method方法声明为虚方法,就是为了今后的扩展的,其子类如果觉得Base类中Method方法的默认实现不合适的话,可以自行override,从而达到扩展的目的。而类B中由于使用了new关键字而不是override,使得在多态调用时调用的仍是基类中的Method而不是子类中的Method。形象的说,由于B没有遵守“游戏规则”,所以B对于Method的扩展不会在通常情况下起作用。
这里插一句,飞林沙提出“那virtual这个关键字就可以废弃了,永远把这个方法给暴露出来就成了,或者使用abstract”
对于这个观点,前半句我将在下面说明;而使用abstract就要求子类必须override父类中的方法,而不能使用一种默认的实现,如果只是声明为virtual,子类就可以选择是否覆盖父类的这个方法。
Hack类库
个人认为,只有这一种情况我们可能用到new关键字来进行函数隐藏,且看代码:
public class 鱼 { public void Swim() { Console.WriteLine("摆尾巴游"); } } public class 章鱼 : 鱼 { //章鱼没有尾巴,怎么办? public void new Swim() { Console.WriteLine("摆触手游"); } }
显然,在设计“鱼”这个类的时候,没有想到会有鱼没有尾巴,但是我们又确实碰见了没有尾巴的“章鱼”(别较真)。
如果“鱼”这个类是在类库中的话,我们就不能修改这个类,于是我们只能用new关键字进行方法隐藏,从而在表面上差不多的情况下使得“章鱼”有正确的Swim效果。也就是说,我认为,只有在类库设计不当,导致在本应该有virtual声明而没有声明的情况下,才需要子类进行方法隐藏。我不知道我这个观点是不是全面,希望大家补充,还有什么情况下可能会用到方法隐藏。
但是方法隐藏有这样一个问题(第一段中提出),即在以基类的签名调用子类时调用的仍然是基类的方法(如((鱼)章鱼A).Swim())。也就是说,只有在我们知道一个实例是该子类的时候,我们才能够调用这个新声明的方法。既然如此,我们为什么不干脆声明一个新的方法,而选择隐藏基类的方法呢?
讨论原则
需要注意的是,我的问题并不是override和new的区别,而是以下两点:
- 方法隐藏的使用时机是什么?
- 既然只有在知道类型签名的时候才能够调用新声明的方法,我们为什么不干脆声明一个新方法?
在弄明白我问的是什么的情况下,希望大家能够积极讨论,在讨论的时候,认真阅读其他人的观点,对症下药,不要答非所问,俗称打岔。
补充
首先来看一些我个人认为比较有意义的回复:
引用Rouper:我也有过一次,不过后来改成显示实现接口来做了。只有在类库的设计太差的时候才会导致非用new不可。感觉有点像是在hack
我觉得如果用了接口的话,那么用显示接口来做是最好的。其实我推荐这种设计方式:<>,<>,<>。
引用飞林沙:
关键是这样的转换可以避免再重新New一个耗时的大对象。我觉得这是关键飞林沙的观点在于为什么不使用组合而是使用继承,但是我感觉跟函数隐藏没有什么特别的联系,不知道是不是我理解的不对。
引用周中豪:
当要求子类B必须具有某个给定方法名的方法供B自身调用或者供其他部分反射,同名方法又已经在父类A中存在时,为了不影响把B视为A时的逻辑,应该用new。不过 这种情况是一种hack,还是不好的设计。的确如此,但是我想知道是不是有一种应该(适合)使用函数隐藏的情况,如果没有的话,是不是应该禁用这个语言特性。
引用CFan.Net:呵呵,我也谈谈我的理解吧。
1、什么时候使用这是一个应用的问题,不要从语言层面的技术角度去思考,那样就钻牛角尖了;
2、举一个游戏的例子,Dosomething表示使用武器,默认的基本武器是刀A,人物在地上拣了一个长枪B, 于是他可以使用B.Dosomething,但是当他走到一个狭窄的巷子里,或是其它什么原因暂时受限制,不能用枪B时,这时调用((A)B).Dosomething临时用刀A了,而正常情况则B.Dosomething;
我认为这个例子明显不能说明问题,这个例子中一个人显然应该拥有若干种武器,什么时候应该用什么武器直接切换就是了,范不着这么别扭。
引用Alex He:new关键字告诉编译器,子类中的方法虽然与父类方法一样(签名,参数),但是该方法与基类中的方法没有任何关系
这句话短小精辟的揭示了函数隐藏的本质。
引用诺贝尔:首先,章鱼不是鱼。因此这个“强硬”的继承,不过是滥用语法的一个特征。语法能支持的,不等于逻辑能通过。
ms设计这个new,自然有他的用意所在。但是我看不到很有意义的设计点。
我个人的看法,这不过是具体编码中的一种妥协,比如,这个方法虽然不行,但是还有其他方法可以,而既然这个方法不行(也就是没有多态意义),那我完全也不用考虑要避免和基类同名,因为这个名字可能还是很能达意的。
当然,我不认为这种妥协是什么好设计,因为这等于放弃了面向对象的哲学,沦为一种复制粘贴,是一种简陋,容易出错的复用代码行为
观点相同~
下面评论中Rouper的思想个人觉得比较好,希望大家能够多加关注:)
另外加一个诺贝尔的链接:不要new你的函数。虽然个别语句略显混乱,不过总体上来说说明了一些问题。
下面给大家带来Zhenway的精彩回复!
引用Zhenway: new有new的用处,不要一棍子打死例如:
public class A { //... public virtual A Clone() { //... } } public class B : A { //... public override A Clone() { //... } }
看起来不错,但用起来哪?
B b1 = new B(); B b2 = (B)b1.Clone();
是不是很别扭?
稍加改造:
public class A { //... public A Clone() { return CloneCore(); } protected virtual A CloneCore() { //... } } public class B : A { //... public new B Clone() { return (B)CloneCore(); } protected override A CloneCore() { //... } }
这样用起来就舒服了吧:
B b1 = new B(); B b2 = b1.Clone(); // another instance of B as type B A a1 = b1; A a2 = a1.Clone(); // another instance of B as type A
引用HCOONa:
@Alex He
我上面是这样说的
我认为你说的这种情况不可能出现,原因如下
1.、如果我使用的是父类的签名,那么你子类即使使用函数隐藏也不会起到任何效果
2、如果我使用的是子类,不知道在.NET2.0上是怎么满足我的要求的。退一步说,就算真的需要改变逻辑,那么说明这个子类的行为特征与父类不相符合,属于一种逻辑上的错误,不应该使用继承
3.补充一下,
而且,为了代码兼容性,我们更应该使用扩展方法,或者添加一个新方法的方式来扩充子类
下面我来解释一下
假设你是一个类库提供者,
对于第一种情况
如果客户是这样使用的:
public class ClassInBLL { private Base base = null; public ClassInBLL(Base base) : base(base) {} }
那么,在子类中使用new关键字根本就不起作用。
对于第二种情况:
原来是这样的:
public class Base { public void Method() { Console.WriteLine("Method in base"); } } public class A : Base { }
并且在客户的代码中是直接使用A的,现在你觉得使用A.Method()时候的Base::Method方法不合用了,那么你需要修改子类,为子类添加一个同名方法Method,以使得在客户的代码不需要改动,只需要重新编译。
现在,我建议进行这样的修改:
public class AA : Base { } public class A { private AA ori_a = new AA() public void Method() {} }
如果你说A必须得继承自Base,那么你那种修改方式也是有问题的。
对于我说的第三点,我建议提供扩展方法来扩展A,而不必修改任何代码,当然,这种方式的前提是,修改Method方法不是必须的,它能够满足部分功能,但是新的需求导致我们需要提供一个新的Method1方法,这个方法可以使用扩展方法满足:)
总结
讨论了这么长时间,我说一下我得出的结论吧,不同意的也不要喷,毕竟大家是可以有不同想法的,不同想法就可以讨论。
我得出的结论有以下两点:
- 函数隐藏只是用于上面Zhenway提到的那种,子类由于调用方便,需要声明一个与父类中同名函数返回值不同的函数,而这两个函数除了返回值不同以外,在逻辑上没有差别:)
- 其他情况不要使用函数隐藏,因为总有更好的解决方案可以解决而没有函数隐藏带来的副作用:)
2012/11/19补充:
在翻旧文的时候突然看到了这篇文章,Zhenway的回复中的内容,现在可以通过协变性完美的解决:
没有方法级的协变性支持,还是做不到更简洁的实现方式。
2012/11/20补充: