Effective C# 原则29:仅在对基类进行强制更新时才使用new修饰符
Item 29: Use the new Modifier Only When Base Class Updates Mandate It
你可以用new修饰符来重新定义一个从基类中继承来的非虚成员。你可以这样做,但并不意味着需要这样做。重新定义非虚方法会导致方法含意的混乱。如果两个相关的类是继承关系,那么很多开发人员可能会立即假设两段代码块是做完全相同的事情,而且他们也会这么认为:
object c = MakeObject( );
// Call through MyClass reference:
MyClass cl = c as MyClass;
cl.MagicMethod( );
// Call through MyOtherClass reference:
MyOtherClass cl2 = c as MyOtherClass;
cl2.MagicMethod( );
一但使用了new修饰符以后,问题就完全不一样了:
public class MyClass
{
public void MagicMethod( )
{
// details elided.
}
}
public class MyOtherClass : MyClass
{
// Redefine MagicMethod for this class.
public new void MagicMethod( )
{
// details elided
}
}
这样的实际操作会让很多开发人员迷惑。因为当你在同一个对象上调用相同的函数时,一定希望它们执行同样的代码。但实际上是,一但你用不同的引用来调用同名的函数,它们的行为是不一样的,这感觉非常糟糕。它们是不一致的。一个MyOtherClass类型的对象所表现的行为会因为你引用的方式不一样而有所不同。这就是new修饰符用在非虚成员上的后果。其实这只是让你在类的名字空间中添加了一个不同的方法(虽然它们的函数名是相同的)。
非虚方法是静态绑定的,不管哪里的代码,也不管在哪里引用,MyClass.MagicMethod() 总是严格的调用类中所定义的函数。并不会在运行时在派生类中查找不同的版本。另一方面,虚函数动态的。运行时会根据不同的类型对象调用不同的版本。
建议大家避免使用new修饰符来重新定义非虚函数,这并不要太多的解释,就像推荐大家在定义一个基类时应该用虚方法一样。一个类库的设计者应该按照合某种约设计虚函数。也就表示你期望任何派生类都应该修改虚函数的实现。虚函数的集合就相当于是定义了一个行为的集合,这些行为是希望在派生中重新实现的。设计默认的虚函数就是说派生可以修改类中的所有虚的行为。这确实是说你不想考虑所有派生类可能要修改行为的分歧问题。相反,你可以把时间花在考虑把什么样的方法以及属性设计成多态的。当然,只有它们是虚行为的时候才能这样做。不要考虑这样会限制类的用户。相反,应该认为这是给类型的用户定义行为提供了一个入口向导。
有且只有一种情况要使用new修饰符,那就是把类集成到一个已经存在的基类上时,而这个基类中已经使用了存在的方法名,这时就要使用new了(译注:就是说基类与派生类都已经存在了,是后来添加的继承关系,结果在添加继承关系时,发现两个类中使用了同样的方法名,那么就可以在派生类中添加一个new来解决这个问题)。因为有些代码已经依懒于类的方法名,或者已经有其它程序集在使用这个方法。例如你在库中创建了下面的类,使用了在另一个库中定义的BaseWidget:
public class MyWidget : BaseWidget
{
public void DoWidgetThings( )
{
// details elided.
}
}
你完成了你的widget, 而且用户可以使用它。然而你却发现BaseWidget公司发布了一个新的版本。而这正是你所渴望的,于是你立即购买并编译你的MyWidget类。结果失败了,因为BaseWidget的家伙们已经添加了他们自己的DoWidgetThings 方法:
public class BaseWidget
{
public void DoWidgetThings()
{
// details elided.
}
}
这是个难题,你的基类中隐藏了一个方法,而这又是在你的类的名字空间中。有两个方法解决这个问题,一个就是修改你的类中的方法名:
public class MyWidget : BaseWidget
{
public void DoMyWidgetThings( )
{
// details elided.
}
}
或者使用new修饰符:
public class MyWidget : BaseWidget
{
public new void DoWidgetThings( )
{
// details elided.
}
}
如果你可以拿到所有使用MyWidget类的源代码,那么你应该选择修改方法名,因为这对于今后的运行会更简单。然而,如果你已经向全世界的人发布了MyWidget类,这会迫使所有用户来完成这个众多的改变。这正是new修饰符容易解决的问题,你的用户不用修改DoWidgetThings()方法而继续使用它。没有人会调用到BaseWidget.DoWidgetThings()方法,因为(对于派生类而言)它们根本不存在。在更新一个基类时,如果发现它与先前申明的成员发生了冲突,可以用new修饰符来解决这个问题。
当然,在某些时候,你的用户可能想调用基类的Widget.DoWidgetThings()方法,这时你又回到了原来的问题上:两个方法看上去是一样的,但其实是不同的。考虑到new修饰长期存在的歧意问题,有时候,还是在短期上麻烦一下,修改方法名为上策。(译注:长痛不如短痛。呵呵)
new修饰符必须小心谨慎的使用。如果它是有歧意的,你就在类上创建了个模糊的方法。这只有在特殊情况下才使用,那就是升级基类时与你的类产生冲突时。即使在这种情况下,也应该小心的使用它。最重要的是,其它任何时候都不要用它。
==========================
Item 29: Use the new Modifier Only When Base Class Updates Mandate It
You use the new modifier on a class member to redefine a nonvirtual member inherited from a base class. Just because you can do something doesn't mean you should, though. Redefining nonvirtual methods creates ambiguous behavior. Most developers would look at these two blocks of code and immediately assume that they did exactly the same thing, if the two classes were related by inheritance:
object c = MakeObject( );
// Call through MyClass reference:
MyClass cl = c as MyClass;
cl.MagicMethod( );
// Call through MyOtherClass reference:
MyOtherClass cl2 = c as MyOtherClass;
cl2.MagicMethod( );
When the new modifier is involved, that just isn't the case:
public class MyClass
{
public void MagicMethod( )
{
// details elided.
}
}
public class MyOtherClass : MyClass
{
// Redefine MagicMethod for this class.
public new void MagicMethod( )
{
// details elided
}
}
This kind of practice leads to much developer confusion. If you call the same function on the same object, you expect the same code to execute. The fact that changing the reference, the label, that you use to call the function changes the behavior feels very wrong. It's inconsistent. A MyOtherClass object behaves differently in response to how you refer to it. The new modifier does not make a nonvirtual method into a virtual method after the fact. Instead, it lets you add a different method in your class's naming scope.
Nonvirtual methods are statically bound. Any source code anywhere that references MyClass.MagicMethod() calls exactly that function. Nothing in the runtime looks for a different version defined in any derived classes. Virtual functions, on the other hand, are dynamically bound. The runtime invokes the proper function based on the runtime type of the object.
The recommendation to avoid using the new modifier to redefine nonvirtual functions should not be interpreted as a recommendation to make everything virtual when you define base classes. A library designer makes a contract when making a function virtual. You indicate that any derived class is expected to change the implementation of virtual functions. The set of virtual functions defines all behaviors that derived classes are expected to change. The "virtual by default" design says that derived classes can modify all the behavior of your class. It really says that you didn't think through all the ramifications of which behaviors derived classes might want to modify. Instead, spend the time to think through what methods and properties are intended as polymorphic. Make thoseand only thosevirtual. Don't think of it as restricting the users of your class. Instead, think of it as providing guidance for the entry points you provided for customizing the behavior of your types.
There is one time, and one time only, when you want to use the new modifier. You add new to incorporate a new version of a base class that contains a method name that you already use. You've already got code that depends on the name of the method in your class. You might already have other assemblies in the field that use this method. You've created the following class in your library, using BaseWidget that is defined in another library:
public class MyWidget : BaseWidget
{
public void DoWidgetThings( )
{
// details elided.
}
}
You finish your widget, and customers are using it. Then you find that the BaseWidget company has released a new version. Eagerly awaiting new features, you immediately purchase it and try to build your MyWidget class. It fails because the BaseWidget folks have added their own DoWidgetThings method:
public class BaseWidget
{
public void DoWidgetThings()
{
// details elided.
}
}
This is a problem. Your base class snuck a method underneath your class's naming scope. There are two ways to fix this. You could change that name of your DoWidgetThings method:
public class MyWidget : BaseWidget
{
public void DoMyWidgetThings( )
{
// details elided.
}
}
Or, you could use the new modifier:
public class MyWidget : BaseWidget
{
public new void DoWidgetThings( )
{
// details elided.
}
}
If you have access to the source for all clients of the MyWidget class, you should change the method name because it's easier in the long run. However, if you have released your MyWidget class to the world, that would force all your users to make numerous changes. That's where the new modifier comes in handy. Your clients will continue to use your DoWidgetThings() method without changing. None of them would be calling BaseWidget.DoWidgetThings() because it did not exist. The new modifier handles the case in which an upgrade to a base class now collides with a member that you previously declared in your class.
Of course, over time, your users might begin wanting to use the Base Widget.DoWidgetThings() method. Then you are back to the original problem: two methods that look the same but are different. Think through all the long-term ramifications of the new modifier. Sometimes, the short-term inconvenience of changing your method is still better.
The new modifier must be used with caution. If you apply it indiscriminately, you create ambiguous method calls in your objects. It's for the special case in which upgrades in your base class cause collisions in your class. Even in that situation, think carefully before using it. Most importantly, don't use it in any other situations.