构造函数中的方法,不好?

Spo*_*oks 20 c# coding-style structure

我有一个Windows窗体,我有一个类检查文本文件,以确保它具有某些方面.现在我在构造函数中有方法,它看起来有点奇怪.我应该将构造函数留空,并实现一个start()类型方法并调用它?到目前为止我的代码看起来像这样

public class Seating
{
    private int segments = 0;
    public Seating()
    {
        checkInvoice();
        getSegmentCount();          
    }
}
Run Code Online (Sandbox Code Playgroud)

Kon*_*lph 27

我在构造函数中有方法,看起来有点奇怪

那么,为什么还有构造函数存在呢?在构造函数中使用方法是完全合法的,甚至是可能失败的方法(从而中断对象创建).很难说这是否适合您的具体情况.

一般情况下它很好.

  • +1:除了其他人所说的虚拟方法之外. (5认同)
  • @Lucero:我不同意.最好的做法是如果处理一次性资源,保持构造函数*清理*(不干净).当然,未创建的对象不能泄漏资源,但完全可以在构造函数本身中防止这种情况(并且*应该*绝对完成!).构造函数应该完成它为使对象处于初始化状态所必须做的所有工作.如果这需要处理可以抛出异常的代码,那就这样吧.如果构造函数将对象初始化了一半(如您所建议的那样),那么它就会被破坏. (3认同)
  • @Lucero:但请注意,Herb 的文章特别*没有*说结果是削弱构造函数。他只是说要小心处理不当。第二个和第三个链接不会以任何方式转发任何参数,它们只是列出选项(尽管第三个链接明确警告您所提倡的部分构造对象的危险)。这三篇文章都很有趣,但没有一篇支持你关于削弱构造函数的论点(你所说的“干净和精益”)。 (2认同)
  • @Konrad,我说你傲慢,因为你把话放在我嘴里,而不是坚持你的观点。但回到主题:如果我理解正确的话,您还会发现 `WebRequest`/`WebResponse` 后代、完整的 XML DOM、网络套接字和更多 API 设计得很糟糕?所有这些都适用于创建-设置-使用工作流程(相对于仅创建-使用)。我看到了这两种方法的好处,对我来说,这两种方法都没有异味。部分初始化的对象比尚未设置的对象更丑陋(即使构造函数似乎总是分配一个值等,只读字段也可能为空)。 (2认同)
  • @Lucero:非常简短地回答:是的.它们都设计得很糟糕.应为*builder*实例保留设置对象.关键是,当程序最初设计时,并非所有这些都是已知的.我们从我们(和其他人)的错误中吸取教训. (2认同)

Ada*_*son 8

在构造函数中调用非虚方法没有错.在构造函数触发时,您的父对象已完全构造.但是,您希望调用任何虚方法,因为它们可以被子类覆盖,子类在未完全构造时会在其中执行代码.

  • 如果它们不使用任何必须初始化的实例成员,则在构造函数中调用虚拟成员没有问题。 (2认同)

Mel*_*igy 7

一般规则是方法非常简单,不太可能在构造函数中抛出异常.如果方法可能由于任何原因(文件访问或丢失,数据库问题,空引用......)而失败,那么它不应该在构造函数中,因为人们应该期望构造函数不会失败(特别是无参数构造函数,尽管一般指导没有指明).人们不应该期望var seating = new Seating();成为错误的来源.

您可以添加Start()/ Initialize()种类的方法作为您提到的实例方法.您还可以添加一个新的静态方法,该方法在调用您需要的两个方法并返回构造函数私有之后返回Class的新实例.您可以更进一步,在新的Factory类中使用此方法(使构造函数内部).您也可以考虑其他方法.

有一点是你可以看到在这些方法中计算或检索任何值的构造函数参数(并且没有无参数构造函数),然后构造函数将只将这些参数分配给相应的字段/属性.同一程序集或其他"服务"专用程序集中的工厂方法或服务方法可以负责调用方法,获取参数,将它们传递给构造函数,以及返回类的新实例.这是我个人的最爱.

一般来说,这类问题表明该类做得太多,您可能希望将功能拆分为其他类(现有的或新的).这就是建议最后一个解决方案的原因,因此这两个方法本身可能位于另一个"服务"程序集中,但如上所述,如果需要,还有许多其他方法可以执行此操作.

更新:

以下是Microsoft构造函数指南:
构造函数使用指南

从页面引用:

最小化构造函数中完成的工作量.构造函数不应该只捕获构造函数参数或参数.这会延迟执行进一步操作的成本,直到用户使用实例的特定功能.

更新2

上面的页面在Constructor Design的名称下移动到一个活文档(这意味着它是可更新的).

  • 这是错的.如果你说出为什么你认为"人们不应该期望`var seat = new Seating();`是错误的根源." - 一般来说,他们没有理由不这样做,这可能会有所帮助.引入一个`Start`方法并没有使这更好(事实上更糟).让一个对象处于一个不正确的初始化状态是一个反模式,虽然很多旧的API都这样做,但你不应该这样做. (2认同)
  • 您引用的构造函数使用指南是为.NET 1.1编写的,并没有真正说明异常.另一方面,.NET 4.5的[这些构造函数设计指南](http://msdn.microsoft.com/en-us/library/ms229060%28v=vs.110%29.aspx)提到"**DO**如果合适的话,从实例构造函数中抛出异常." (2认同)

Bry*_*yan 6

一般来说,构造函数应该只设置对象的状态.这可能意味着调用方法.我会说运行很长的方法并不是一个好主意.您的类的用户不希望构造函数是一种昂贵的方法.

我不是你没有提到虚方法,但你永远不应该在构造函数中调用它们.

以下是如何快速恢复的示例:

public class MyBase
{
        protected MyBase()
        {
                this.VirtualMethod();
        }

        protected virtual void VirtualMethod()
        {
                Console.WriteLine("VirtualMethod in MyBase");
        }
}

public class MyDerived : MyBase
{
        private readonly string message = "Set by initializer";

        public MyDerived(string message)
        {
                this.message = message;
        }

        protected override void VirtualMethod()
        {
                Console.WriteLine(this.message);
        }
}
Run Code Online (Sandbox Code Playgroud)

现在,让我们说你在其他地方有这个代码:

MyDerived d = new MyDerived("Called from constructor");
Run Code Online (Sandbox Code Playgroud)

您认为在控制台上会显示什么?如果您说"由初始化程序设置",那么您是对的.

这就是为什么:

  • 所有字段初始值设定项都在构造函数中的代码之前执行.
  • C#编译器在用户定义的任何内容之前添加对基础构造函数的调用.在这种情况下,它调用
    MyBase哪个调用VirtualMethod().由于d的运行时类型是MyDerived,的超驰VirtualMethod()MyDerived
    执行.现在,由于MyDerived构造函数的主体尚未
    执行,因此this.message具有在初始化程序中给出的值
    .
  • 现在MyDerived执行的构造函数的主体.
  • 稍后VirtualMethod()对此实例的
    调用现在将打印出"从构造函数调用".


Luc*_*ero 5

构造函数中的虚方法调用是不行的(密封类的极小例外,这使得该方法实际上是非虚拟的).

非虚方法可以是好的,但你应该确保所有成员之前已经初始化,以及非虚方法可能不调用任何虚拟成员也是如此.