C# 构造函数中的虚拟成员调用
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/119506/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Virtual member call in a constructor
提问by JasonS
I'm getting a warning from ReSharper about a call to a virtual member from my objects constructor.
我从 ReSharper 收到关于从我的对象构造函数调用虚拟成员的警告。
Why would this be something not to do?
为什么这是不应该做的事情?
采纳答案by Greg Beech
When an object written in C# is constructed, what happens is that the initializers run in order from the most derived class to the base class, and then constructors run in order from the base class to the most derived class (see Eric Lippert's blog for details as to why this is).
当用 C# 编写的对象被构造时,发生的情况是初始化器从最派生的类到基类依次运行,然后构造函数从基类到最派生的类依次运行(详见 Eric Lippert 的博客)至于为什么会这样)。
Also in .NET objects do not change type as they are constructed, but start out as the most derived type, with the method table being for the most derived type. This means that virtual method calls always run on the most derived type.
同样在 .NET 中,对象在构造时不会改变类型,而是从最派生的类型开始,方法表用于最派生的类型。这意味着虚方法调用总是在派生程度最高的类型上运行。
When you combine these two facts you are left with the problem that if you make a virtual method call in a constructor, and it is not the most derived type in its inheritance hierarchy, that it will be called on a class whose constructor has not been run, and therefore may not be in a suitable state to have that method called.
当您结合这两个事实时,您会遇到一个问题,即如果您在构造函数中进行虚方法调用,并且它不是其继承层次结构中派生程度最高的类型,那么它将在其构造函数尚未被调用的类上调用运行,因此可能不适合调用该方法。
This problem is, of course, mitigated if you mark your class as sealed to ensure that it is the most derived type in the inheritance hierarchy - in which case it is perfectly safe to call the virtual method.
当然,如果您将类标记为密封的以确保它是继承层次结构中派生程度最高的类型,那么这个问题当然会得到缓解 - 在这种情况下,调用虚拟方法是完全安全的。
回答by 1800 INFORMATION
Because until the constructor has completed executing, the object is not fully instantiated. Any members referenced by the virtual function may not be initialised. In C++, when you are in a constructor, this
only refers to the static type of the constructor you are in, and not the actual dynamic type of the object that is being created. This means that the virtual function call might not even go where you expect it to.
因为在构造函数完成执行之前,对象还没有完全实例化。虚拟函数引用的任何成员都可能不会被初始化。在 C++ 中,当您在构造函数中时,this
仅指您所在构造函数的静态类型,而不是正在创建的对象的实际动态类型。这意味着虚函数调用甚至可能不会到达您期望的位置。
回答by David Pierre
Yes, it's generally bad to call virtual method in the constructor.
是的,在构造函数中调用虚方法通常是不好的。
At this point, the objet may not be fully constructed yet, and the invariants expected by methods may not hold yet.
此时,对象可能尚未完全构建,方法所期望的不变量可能尚未成立。
回答by Matt Howells
In order to answer your question, consider this question: what will the below code print out when the Child
object is instantiated?
为了回答你的问题,请考虑这个问题:当Child
对象被实例化时,下面的代码会打印出什么?
class Parent
{
public Parent()
{
DoSomething();
}
protected virtual void DoSomething()
{
}
}
class Child : Parent
{
private string foo;
public Child()
{
foo = "HELLO";
}
protected override void DoSomething()
{
Console.WriteLine(foo.ToLower()); //NullReferenceException!?!
}
}
The answer is that in fact a NullReferenceException
will be thrown, because foo
is null. An object's base constructor is called before its own constructor. By having a virtual
call in an object's constructor you are introducing the possibility that inheriting objects will execute code before they have been fully initialized.
答案是实际上 aNullReferenceException
会被抛出,因为它foo
是 null。一个对象的基构造函数在它自己的构造函数之前被调用。通过virtual
在对象的构造函数中调用,您引入了继承对象在完全初始化之前执行代码的可能性。
回答by Yuval Peled
There's a difference between C++ and C# in this specific case. In C++ the object is not initialized and therefore it is unsafe to call a virutal function inside a constructor. In C# when a class object is created all its members are zero initialized. It is possible to call a virtual function in the constructor but if you'll might access members that are still zero. If you don't need to access members it is quite safe to call a virtual function in C#.
在这种特定情况下,C++ 和 C# 之间存在差异。在 C++ 中,对象未初始化,因此在构造函数中调用虚拟函数是不安全的。在 C# 中,当一个类对象被创建时,它的所有成员都被初始化为零。可以在构造函数中调用虚函数,但如果您可能访问仍然为零的成员。如果您不需要访问成员,则在 C# 中调用虚函数是非常安全的。
回答by Alex Lyman
In C#, a base class' constructor runs beforethe derived class' constructor, so any instance fields that a derived class might use in the possibly-overridden virtual member are not initialized yet.
在 C# 中,基类的构造函数在派生类的构造函数之前运行,因此派生类可能在可能被覆盖的虚拟成员中使用的任何实例字段尚未初始化。
Do note that this is just a warningto make you pay attention and make sure it's all-right. There are actual use-cases for this scenario, you just have to document the behaviorof the virtual member that it can not use any instance fields declared in a derived class below where the constructor calling it is.
请注意,这只是一个警告,让您注意并确保一切正常。这种情况有实际用例,您只需要记录虚拟成员的行为,即它不能使用在调用它的构造函数所在的派生类中声明的任何实例字段。
回答by xtofl
Your constructor may (later, in an extension of your software) be called from the constructor of a subclass that overrides the virtual method. Now not the subclass's implementation of the function, but the implementation of the base class will be called. So it doesn't really make sense to call a virtual function here.
您的构造函数可以(稍后,在您的软件的扩展中)从覆盖虚拟方法的子类的构造函数中调用。现在不是子类的函数实现,而是基类的实现会被调用。所以在这里调用虚函数真的没有意义。
However, if your design satisfies the Liskov Substitution principle, no harm will be done. Probably that's why it's tolerated - a warning, not an error.
但是,如果您的设计满足 Liskov 替换原则,则不会造成任何伤害。可能这就是它被容忍的原因 - 警告,而不是错误。
回答by Lloyd
The rules of C# are very different from that of Java and C++.
C# 的规则与 Java 和 C++ 的规则有很大不同。
When you are in the constructor for some object in C#, that object exists in a fully initialized (just not "constructed") form, as its fully derived type.
当您在 C# 中某个对象的构造函数中时,该对象以完全初始化(只是不是“构造”)的形式存在,作为其完全派生的类型。
namespace Demo
{
class A
{
public A()
{
System.Console.WriteLine("This is a {0},", this.GetType());
}
}
class B : A
{
}
// . . .
B b = new B(); // Output: "This is a Demo.B"
}
This means that if you call a virtual function from the constructor of A, it will resolve to any override in B, if one is provided.
这意味着如果您从 A 的构造函数调用虚函数,它将解析为 B 中的任何覆盖(如果提供)。
Even if you intentionally set up A and B like this, fully understanding the behavior of the system, you could be in for a shock later. Say you called virtual functions in B's constructor, "knowing" they would be handled by B or A as appropriate. Then time passes, and someone else decides they need to define C, and override some of the virtual functions there. All of a sudden B's constructor ends up calling code in C, which could lead to quite surprising behavior.
即使你故意这样设置 A 和 B,完全理解系统的行为,你以后也可能会感到震惊。假设您在 B 的构造函数中调用了虚函数,“知道”它们将由 B 或 A 酌情处理。然后时间过去了,其他人决定他们需要定义 C,并覆盖那里的一些虚函数。突然间,B 的构造函数最终调用了 C 中的代码,这可能会导致非常令人惊讶的行为。
It is probably a good idea to avoid virtual functions in constructors anyway, since the rules areso different between C#, C++, and Java. Your programmers may not know what to expect!
这可能是一个好主意,避免虚函数在构造无论如何,因为规则是C#,C ++和Java之间如此不同。您的程序员可能不知道会发生什么!
回答by Ilya Ryzhenkov
Reasons of the warning are already described, but how would you fix the warning? You have to seal either class or virtual member.
已经描述了警告的原因,但是您将如何修复警告?您必须密封类或虚拟成员。
class B
{
protected virtual void Foo() { }
}
class A : B
{
public A()
{
Foo(); // warning here
}
}
You can seal class A:
您可以密封A类:
sealed class A : B
{
public A()
{
Foo(); // no warning
}
}
Or you can seal method Foo:
或者你可以密封方法 Foo:
class A : B
{
public A()
{
Foo(); // no warning
}
protected sealed override void Foo()
{
base.Foo();
}
}
回答by supercat
One important aspect of this question which other answers have not yet addressed is that it is safe for a base-class to call virtual members from within its constructor if that is what the derived classes are expecting it to do. In such cases, the designer of the derived class is responsible for ensuring that any methods which are run before construction is complete will behave as sensibly as they can under the circumstances. For example, in C++/CLI, constructors are wrapped in code which will call Dispose
on the partially-constructed object if construction fails. Calling Dispose
in such cases is often necessary to prevent resource leaks, but Dispose
methods must be prepared for the possibility that the object upon which they are run may not have been fully constructed.
其他答案尚未解决的这个问题的一个重要方面是,如果派生类希望它这样做,则基类从其构造函数内部调用虚拟成员是安全的。在这种情况下,派生类的设计者负责确保在构造完成之前运行的任何方法在这种情况下都尽可能合理地运行。例如,在 C++/CLI 中,构造函数被包装在代码中,Dispose
如果构造失败,它将调用部分构造的对象。Dispose
在这种情况下调用通常是防止资源泄漏所必需的,但是Dispose
方法必须为运行它们的对象可能尚未完全构造的可能性做好准备。