C#禁止调用默认构造函数的根本原因是什么?

时间:2009-09-16 07:08:12

标签: c#

这是我在这里问到的另一个问题的后续问题: Calling constructor from other constructor in same class at the end

前一个是关于如何,现在问题是为什么微软这样设计它?

更新:我的问题是:

为什么我不能直接在另一个构造函数的结尾处调用构造函数,而我可以在开始时调用。

如果他们最后禁止打电话,为什么他们也禁止在开头直接打电话呢?

7 个答案:

答案 0 :(得分:7)

  

为什么我不能调用构造函数   直接在另一个的结尾   构造函数,而我可以打电话给AT THE   开始。

好吧,让我们将其分解为两个案例。 (1)你正在调用一个“this”构造函数,(2)你正在调用一个“基础”构造函数。

假设您处于第一种情况。这种情况的典型使用模式是让一堆ctors采用不同的参数然后全部“反馈”到一个完成所有实际工作的主ctor(通常是私有的)。通常情况下,公共银行没有自己的机构,因此在空座之前“之前”或“之后”调用另一个ctor没有区别。

假设您处于第一种情况并且 在每个ctor中工作,并且您希望在当前ctor开始之外的其他时间呼叫其他ctors。

在这种情况下,您可以通过将不同ctors完成的工作提取到方法中,然后按照您喜欢的顺序调用ctors中的方法来轻松完成此操作。这比发明一种允许你在任意位置调用其他ctors的语法更胜一筹。有许多设计原则支持这一决定。两个是:

1)有两种方法可以做同样的事情会造成混乱;它增加了心理成本。我们在C#中经常有两种方法可以做同样的事情,但在这种情况下,我们希望通过两种不同的方式来完成这项工作,使每种情况都很有吸引力,有趣且强大的功能具有明显的优势,利弊。 (例如,“查询理解”与“流畅查询”是构建查询的两种截然不同的方式。)有一种方法可以像调用任何其他方法一样调用ctor,这似乎有两种方法可以做某事 - 调用初始化方法 - 但没有引人注目或有趣的“收益”。

2)我们必须添加新的语言语法来完成它。新语法的成本非常高;它必须经过设计,实施,测试和记录 - 这些都是我们的成本。但是你的成本更高,因为你必须学习语法的含义,否则你就无法阅读或维护其他人的代码。那是另一个成本;再次,如果我们认为对我们的客户有明确,引人注目的巨大利益,我们只需要花费大量费用来添加语法。我不认为这里有很大的好处。

简而言之,在不添加功能的情况下轻松实现所需的施工控制流程,并且添加该功能没有任何令人信服的好处。没有新的有趣的代表能力被添加到该语言。

对于“基础”场景,它非常简单。您永远不想在派生构造函数之后调用基础构造函数。这是正常依赖规则的反转。派生代码应该能够依赖于已经设置了对象的“基本”状态的基础构造函数;基本ctor应该永远不依赖于已设置派生状态的派生构造函数。

答案 1 :(得分:4)

Eric Lippert有一个blog post关于创建对象时初始化程序和构造函数运行的顺序。在其中他说:“派生的构造函数可能依赖于由较少派生的构造函数初始化的状态,因此构造函数应该按照从基础到派生的顺序运行”。

答案 2 :(得分:3)

我认为您描述的行为的技术原因在C#规范中暗示:

http://en.csharp-online.net/ECMA-334:_17.10.3_Constructor_execution

任何变量初始值设定项都是作为构造函数代码的一部分调用的,如果你能够直接调用构造函数 - 从编译器的角度来看这是一个非常特殊的函数 - 你可能会触发其他行为。使用构造函数链接方法(在构造函数定义上使用“:this”),编译器会尊重您安全调用另一个构造函数的尝试。

我认为不需要在同一个类中调用另一个构造函数(除了继承) - 如果没有定义另一个函数,没有什么是你做不到的,说实话,我更喜欢这样做。如果我的构造函数变得冗长,我通常会觉得将它推入单独的函数更加舒服,因为我经常需要在构造函数之外调用复杂的“Reset”或“Init”函数。

答案 3 :(得分:2)

从C ++的设计来看,IIRC必须以自上而下的顺序调用构造函数,因为在构造函数完成执行之前,基类实例不能保证可用。除非事先知道基础对象已完全初始化并准备就绪,否则无法安全地构造派生对象。如果这看起来像一个微不足道的点,请考虑基类包含仅在其构造函数中正确初始化的私有状态的情况。 AFAIK的推理在CLR中是相同的。

同样的理由适用于相反的方向。在派生类实例完成后销毁/完成基类实例是唯一安全的,因为派生对象可能依赖于基础对象的状态仍然有效。

答案 4 :(得分:1)

我猜,但可能很简单:你想要它很少非常,当你这样做时,通常有更好的选择(私有/受保护的初始化方法)来充分地完成工作。并且没有忘记初始化它的风险,或者调用依赖于尚未初始化的数据的方法(在virtual中调用ctor方法的原因有点冒险)。

唯一需要注意的是,你不能在readonly初始化方法中使用ctor字段,但对于角落情况来说,这比IMO更复杂。 / p>

答案 5 :(得分:1)

构造函数的目的是初始化对象。 当您想要将一些初始参数传递给对象时,将使用重载构造函数。

因此,设计的目的是让一个Mega构造函数接受所有变量并进行所有初始化,并让其他构造函数传播变量,并将默认值设置为未从用户传递的参数。

我们的想法是让一个方法进行初始化,而不是让每个单独的构造函数执行其他操作。

答案 6 :(得分:1)

默认构造函数只有在您未指定自己的构造函数时才由编译器创建。这是一种语法糖,可以很容易地快速创建一个类。

通过编写自己的构造函数,您实际上声明是的,您对如何初始化对象感兴趣。不生成默认构造函数。

由于只能通过编写另一个构造函数来禁用默认构造函数,因此允许用户调用默认构造函数会使其无法禁用它。