【问题标题】:Can VB.NET be forced to initialize instance variables BEFORE invoking the base type constructor?VB.NET 可以在调用基类型构造函数之前强制初始化实例变量吗?
【发布时间】:2011-01-05 09:25:24
【问题描述】:

在调试 VB.NET 中涉及初始化实例变量的顺序的一个特别棘手的问题后,我发现我对 C# 的预期行为与 VB.NET 中的实际行为之间存在重大差异。

注意事项: 这个问题涉及到 VB.NET 和 C# 的行为略有不同。如果你是一个语言偏执者,除了“这就是你应该使用 C#,菜鸟” 之外无法提供答案,那么这里没有任何内容可供你查看;请继续前进。

具体来说,我预计 C# Language Specification 概述的行为(已添加重点):

当实例构造函数没有构造函数初始值设定项,或者它具有base(...) 形式的构造函数初始值设定项时,该构造函数隐式执行由在其类中声明的实例字段的变量初始值设定项指定的初始化。 这对应于在进入构造函数并在直接基类构造函数的隐式调用之前立即执行的一系列赋值。变量初始值设定项按照它们出现在类声明。

将其与 VB.NET 语言规范中关于Instance Constructors 的部分进行对比,其中说(添加了重点):

当构造函数的第一条语句为MyBase.New(...) 形式时,构造函数隐式执行由类型中声明的实例变量的变量初始化器指定的初始化。 这对应于在调用直接基类型构造函数后立即执行的一系列赋值。 这样的排序确保所有基实例变量在任何有权访问该实例的语句之前由其变量初始化器初始化执行。

这里的差异是显而易见的。 C# 在调用基本构造函数之前 初始化类级变量。 VB.NET 正好相反,显然更喜欢在设置实例字段的值之前调用基本构造函数

如果您想查看一些代码,this related question 提供了一个更具体的发散行为示例。不幸的是,它没有提供任何关于如何强制 VB.NET 遵循 C# 建立的模型的提示。

我对这两种语言的设计者为什么选择如此不同的方法感兴趣,而不是对问题的可能解决方法感兴趣。最终,我的问题如下:有什么方法可以在 VB.NET 中编写或构造我的代码,以强制实例变量在 在调用基类型的构造函数之前进行初始化 strong>,就像 C# 中的标准行为一样?

【问题讨论】:

  • 我不相信有可能改变这一点 - VB 编译器将反对任何这样做的尝试。如果您依赖这种行为,通常表明代码中的其他地方存在问题。长期以来一直有反对在构造函数中调用虚拟成员的建议(如另一个问题中的示例代码)
  • @Damien:你说得对,在构造函数中调用虚拟成员是不好的做法。不幸的是,这个决定不是我做的。我继承了框架提供的一种类型,同时还覆盖了它的一个虚拟方法。我很确定问题出在我的代码之外,而我唯一能想到的解决这个问题的方法就是对我来说是“丑陋的黑客”。

标签: .net vb.net constructor initialization c#-to-vb.net


【解决方案1】:

如果您有要在构造期间调用的虚拟成员(反对最佳建议,但我们已经同意),那么您需要将您的初始化移动到一个单独的方法中,这样可以保护自己免受多次调用(即如果 init 已经发生,则立即返回)。然后,该方法将由虚拟成员和您的构造函数调用,然后再依赖已发生的初始化。

这有点混乱,可能会带来轻微的性能损失,但您在 VB 中几乎无能为力。

【讨论】:

    【解决方案2】:

    任何编写良好的类都必须确保可能在部分构造的实例上调用的任何虚拟成员的行为都合理。 C# 的理念是,其字段初始化程序已运行的类实例将处于足够合理的状态以允许使用虚拟方法。 VB.net 的理念是允许字段初始值设定项使用部分构造的对象(并且——通过一些工作——传递给构造函数的任何参数)比保证字段初始值设定项将在任何虚拟对象之前运行更有用方法被调用。

    恕我直言,从语言设计的角度来看,正确的方法应该是提供一种方便的方法来指示指定的字段初始化程序应该“早”或“晚”运行,因为有时每个都可能有用(尽管我更喜欢“后期”风格,因为它允许构造函数参数可用于字段初始值设定项)。例如:

    Class ParamPasserBase(Of T) ' 用于传递一个构造函数参数的通用类 受保护的构造函数Param1 As T 子新(参数作为 T) ConstructorParam1 = 参数 结束子 结束类 MyThing 类 继承 ParamPasserBase(Of Integer) 将 MyArray(ConstructorParam1-1) 调暗为字符串 Sub New(ArraySize As Integer) MyBase.New(ArraySize) 结束子 ... 结束类

    在 C# 中,没有很好的方法让字段声明或初始化程序使用传递给构造函数的参数。在 vb 中,它可以如上图所示合理干净地完成。请注意,在 vb 中,也可以在运行任何字段初始化程序之前使用按引用构造函数参数来偷运正在构造的对象的副本;如果一个类的 Dispose 例程被正确编写以处理部分构造的对象,则可以正确清理在其构造函数中抛出的对象。

    【讨论】:

    • 我不确定我是否听懂了您在此处所说的内容。我没有能力重新设计这两种语言,我也没有特别争论这些选择的相对优点。此外,您的代码示例中的RIAA() 来自哪里?您说现在可以在 VB.NET 中执行此操作。我不熟悉语法。 RIAA() 是您编写的自定义包装类吗?这与using 声明有何不同?除此之外,“旧”VB.NET 和“新”VB.NET 之间的区别是什么?一个是类型安全的,另一个不是?破坏与这个问题有什么关系?
    • @Cody Gray:RIAA 是一个类方法,它注册一个 IDisposable 用于确定性自动清理(以 C++/RIAA 的风格),然后返回它。在 vb.net 中,字段初始值设定项在一次性列表创建后运行,因此 RIAA 方法可以将它们添加到该列表中。 VB.Net 还允许将正在构造的对象从构造函数中偷运出来,因此可以将构造函数包装在工厂方法中,如果构造函数抛出,该方法将清除所有已注册的 IDisposable 对象。
    • 你能给我一个链接到哪里讨论这种 RIAA 方法吗?我在网上找不到文档。我也不确定在调用基类构造函数之前处理项目与初始化实例变量有什么关系。我的问题甚至与对象无关,更不用说那些实现IDisposable 的对象了。测试用例其实是IntegerBoolean类型,都是值类型,不需要dispose。 RIAA 在这里既无用又无关紧要。
    • @Cody Gray:对不起,我不清楚。 RIAA 是我的一些类中的一种方法,当与其他代码结合使用时,它可以实现所述功能。我的观点是,vb.net 允许在一行代码中声明、初始化和清理字段,而不必将源代码的一个区域用于对象声明,另一个区域用于初始化,以及第三个区域用于清理。将一个字段的所有三个方面放在一起似乎比将它们分开更简洁且不易出错。
    • 我还是不明白。首先,您在自己的程序中实现的方法对我有什么帮助?其次,实施这种方法背后的基本原理让我无法理解。您可以将对象包装在 using 语句中,这具有限制其范围的额外好处。我对语言的语法没有任何问题;我的问题是规范中描述的行为。第三,这与没有实现IDisposable 的值类型有什么关系?我用一些示例代码链接到的问题不是调用任何构造函数。 RIAA 在那里不会有任何好处。
    猜你喜欢
    • 1970-01-01
    • 2015-04-28
    • 2013-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多