【问题标题】:C#: Why can't I pass 'this' as a constructor argument to a base class?C#:为什么我不能将“this”作为构造函数参数传递给基类?
【发布时间】:2015-04-22 17:12:07
【问题描述】:

您不能将 this 作为参数传递给基本构造函数 - 参见例如 C# language specification section 10.10.1 Constructor initializers(页面最后一行)。

我不明白这个限制,我愿意。在 C# 中,与 C++ 不同,正在构造的实例已经是它的实际类型并且“一切正常”(当然不是一切都被初始化;我的意思是在派生类的构造函数中调用的虚函数执行派生类的方法)。即使派生类的覆盖将被执行并且派生类可能还没有准备好,基类也可以调用自己的虚拟方法;这不排除。那么这个限制的原因是什么?

(在 C++ 中允许这样做有三个原因。第一,用户或派生类应该知道他在做什么。第二,C++ 的用户应该知道他在做什么。第三,即使用户不知道他在做什么 C++ 的哲学是给他绳子他需要吊死自己,然后在他打结时帮助他。我真的喜欢这个哲学!)

顺便说一句,我想要做的是在循环链表中构造正确初始化的列表成员。有一个基类 Element 和一个字段 Link 指向下一个元素。有一个类Head 派生自Element,它将是列表开头的杰出哨兵,并对整个列表具有特殊行为。 Element 的构造函数将列表的头部作为参数,我想写如下以正确初始化元素和头部:

class Element {
    protected Element link;
    public Element(Element prior) 
    {
        this.link = null;
        prior.link = this;
    }
};

class Head : Element {
    public Head() : base(this) {}
};

(在你抱怨我应该以不同的方式做这个例子之前,我的实际代码有点复杂 - 一个专门的稀疏数组 - 我有我的理由。)我将使用工厂的列表(以及工厂方法前往添加元素)来避免这种情况,这可以说是更好的设计,但我很困惑这种其他合理的方法被取缔了。

【问题讨论】:

  • 您正在传递对未初始化对象的引用。完全有太多可能的事故,语言禁止它。
  • @Hans:它不是未初始化的:在调用实例构造函数之前,所有字段都有其分配的值(由于变量初始化器)或默认值(section 10.10.3)(事实使它与 C++ 不同)。在实例构造函数完成运行之前,实例可能不会语义初始化 - 但是当基类构造函数调用在派生类中重写的虚拟方法时没关系。
  • 在这里争论的意义何在?与 C# 语言设计者交谈。
  • @Hans:对不起,我不是故意的。它认为我在我要求原因的问题中说得很清楚,所以我可以理解。我认为还有很多关于 SO 的其他问题,询问以各种语言做出选择背后的动机。

标签: c# constructor


【解决方案1】:

C# 和 .NET 的设计旨在让基类控制其不变量。一旦基类构造函数完成,派生类代码,包括派生类构造函数,就可以以基类允许的任何方式操作对象的基类部分。除了在基类构造函数调用之前将值存储到它自己的(派生类型)字段之外,派生类不能对正在构造的对象做任何事情,但基类契约实际上可以说“我要调用它在我的构造函数中使用虚方法,任何合法的派生类都必须准备好处理它。

该设计确实存在一些限制。恕我直言,最糟糕的是,在派生类构造代码访问其构造函数参数后,没有任何机制可以让基类断言对构造过程的控制。

C# 在链接到基构造函数之前评估字段初始化表达式,理论上这将允许派生类对象在基构造函数调用它们的虚拟方法之前设置自己;这样做的结果是字段初始化器表达式不能对正在构建的对象做任何事情。 VB.NET 在基本构造函数之后运行初始化程序,这允许更方便的访问,但意味着如果基本构造函数调用任何虚拟方法,字段将被统一化。就个人而言,我认为 C# 方法“几乎”有用,但是无法处理其不变量会受构造函数参数影响的类严重限制了它的用处。

顺便说一句,我真正想看到的是Object 包含一个虚拟方法,该方法将在最派生的构造函数完成执行或引发异常和时间控制返回到代码之间运行致电foo = new Bar();。基类构造函数在派生类构造函数运行之前公开正在构造的对象的主要原因是,没有标准方法可以确保它们在此之后获得控制权。这样的设计将极大地改进构造函数的排序。不过,我不会因为这样的事情而屏住呼吸。

【讨论】:

  • 您写道:“派生类不能对正在构造的对象做任何事情,除了在基类构造函数调用之前将值存储到它自己的 [已声明] 字段......”我假设你是此处仅指 static 字段。这将是一个重要的澄清,因为this 的不可访问性正是问题的症结所在。
  • @GlennSlayden:除非自从我写下答案后事情发生了变化(我没有跟上 C# 或 .NET 的所有变化,因为我唯一的 .NET 编程是维护现有项目)我相信它本质上是正确的。字段初始值设定项设置正在构建的对象的相应字段,但是(除非事情发生了变化)它们不允许形成对正在构建的对象的引用或使用它做任何其他需要这样的操作参考。
  • 哦,我明白了;您在谈论实例字段初始化程序代码(出于不相关的原因,我个人严格避免使用该代码)。最初,通过“做任何事情......之前”,我认为您的主要意思是从内部/在时间 : this(...) constructor call-site context ,因为后者似乎确实满足您的条件“[not...] before the base-class constructor call”(假设基类使用: this(...)构造函数链接)。所以我的评论是半撤回的;你仍然可以做一个小的澄清。
猜你喜欢
  • 1970-01-01
  • 2014-12-25
  • 2023-04-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多