为什么在构造函数中首选对象初始化?

3
在Java/C#中,我们通常看到以下代码:

可能是重复的问题:
C#成员变量初始化,最佳实践?
为什么应该在构造函数中初始化成员变量?
我应该在构造函数内部还是外部初始化变量?

在Java/C#中,通常会看到以下代码:

public class MyClass {
    private MyObject myObject;

    public MyClass(){
        myObject = new MyObject();
    }
}

替代

public class MyClass {
    private MyObject myObject = new MyObject();

    public MyClass(){

    }
}

如果有原因的话,是什么原因?

1
只有在需要时才使用资源。 - Andre Calil
3
通常在Java/C#中我们会看到这种情况。这是一个很大的假设。我会尽可能使用第二种方法而不是第一种。这可能只是你与之合作的人所采用的编程风格。 - Servy
1
在这种情况下,只有在调用构造函数时才会创建对象。基本上,这两个代码将以相同的方式工作。 - Luiggi Mendoza
3
请看这里(http://www.csharp411.com/c-object-initialization/),样例对于Java代码的行为类似。只有当属性具有“static”修饰符时,第一段代码才能使用资源。 - Luiggi Mendoza
1
我个人更喜欢第二个。 - CodesInChaos
显示剩余3条评论
2个回答

1
这归结于编码偏好,正如其中一条评论所述。如果您编译以下代码。
public class TestInitialization
{
    private object test1 = new object();
    private object test2;

    public TestInitialization()
    {
        this.test2 = new object();
    }
}

编译后,实际使用的代码如下

public class TestInitialization
{
    private object test1;
    private object test2;

    public TestInitialization()
    {
        this.test1 = new object();
        this.test2 = new object();
    }
}

所以它们是完全相同的东西,使用任何一个都可以。

编辑: 这里有一个基类和继承类的示例以及生成的编译IL。

基类

class basetest
{
    private object test1 = new object();
    private object test2;

    public basetest()
    {
        this.test2 = new object();
    }
}

继承类

class testclass : basetest
{
    private object testclass1 = new object();
    private object testclass2;

    public testclass() : base()
    {
        this.testclass2 = new object();
    }
}

生成的IL基类

.class private auto ansi beforefieldinit basetest
    extends [mscorlib]System.Object
{
    .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
    {
        .maxstack 8
        L_0000: ldarg.0 
        L_0001: newobj instance void [mscorlib]System.Object::.ctor()
        L_0006: stfld object logtest.basetest::test1
        L_000b: ldarg.0 
        L_000c: call instance void [mscorlib]System.Object::.ctor()
        L_0011: nop 
        L_0012: nop 
        L_0013: ldarg.0 
        L_0014: newobj instance void [mscorlib]System.Object::.ctor()
        L_0019: stfld object logtest.basetest::test2
        L_001e: nop 
        L_001f: ret 
    }


    .field private object test1

    .field private object test2

}

继承类 IL

.class private auto ansi beforefieldinit testclass
    extends logtest.basetest
{
    .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
    {
        .maxstack 8
        L_0000: ldarg.0 
        L_0001: newobj instance void [mscorlib]System.Object::.ctor()
        L_0006: stfld object logtest.testclass::testclass1
        L_000b: ldarg.0 
        L_000c: call instance void logtest.basetest::.ctor()
        L_0011: nop 
        L_0012: nop 
        L_0013: ldarg.0 
        L_0014: newobj instance void [mscorlib]System.Object::.ctor()
        L_0019: stfld object logtest.testclass::testclass2
        L_001e: nop 
        L_001f: ret 
    }


    .field private object testclass1

    .field private object testclass2

}

我觉得我有点困惑。在这个例子中,构造函数外的初始化器在调用基类构造函数之前首先被初始化。因此,无论如何,在构造函数内部的初始化器之前,构造函数外的初始化器都将首先被初始化,对于大多数情况来说,这并不重要。从技术上讲,它们都会被转换为在构造函数内部进行初始化,并遵循以下规则:

  1. 所有构造函数外的初始化器都会首先运行
  2. 调用所有基类构造函数
  3. 运行构造函数内的所有初始化器

基本上,编译器将所有构造函数外的初始化器预置到构造函数代码中,然后按照正常方式运行。

所以这个:

public class test : basetest
{
    private object test1 = new object();
    private object test2;

    public test() : base()
    {
        this.test2 = new object();
    }
}

public class basetest
{
    private object basetest1 = new object();
    private object basetest2;

    public basetest()
    {
        this.basetest2 = new object();
    }
}

变成

public class test : basetest
{
    private object test1;
    private object test2;

    public test()
    {
        //prepend everything first
        this.test1 = new object();

        //call base
        base(); //not legal but just an example

        //everything else that was already here
        this.test2 = new object();
    }
}

public class basetest
{
    private object basetest1;
    private object basetest2;

    public basetest()
    {
        //prepend initializers
        this.basetest1 = new object();

        //if there were more base classes, the constructors would be called here

        //do everything else that was already here
        this.basetest2 = new object();
    }
}

希望这样更有意义并澄清了一些事情。我知道当一些人说它在构造函数之外运行“first”或“outside”时,我有些困惑;实际上,它全部在构造函数内部运行,但调用的顺序会受到影响。

好吧,它并不是完全相同的。它几乎完全相同。但是似乎没有任何重要的边缘情况适用于这个例子。 - Servy
@Servy 能否提供一个边缘案例,以便我理解您的意思?我已经测试了很多使用VS2010编译的示例,并使用Reflector检查了所有内容,它们最终都是相同的;即使是静态变量也会在构造函数中初始化...尽管是一个特殊的静态构造函数称为.cctor而不是.ctor,但仍然是在构造函数中。也许我漏掉了什么。 - vane
1
初始化程序在调用基础构造函数之前运行,构造函数在此之后运行。我写了一个快速的示例来验证这确实是这种情况。 - Servy
@BradBoyce 不确定如何评论其他答案,所以我在这里进行评论。我已经测试了关于构造函数的继承和初始化器。在我的VS2010测试中,所有内容(初始化器)都放在 .ctor 和 .cctor 中,它们是构造函数,因此我还没有看到一个例子不是这种情况。变量在构造函数内部初始化的顺序可能会受到将它们放在构造函数外部和内部的影响,但它们都在 .ctor 和 .cctor 内部得到初始化。 - vane
@Servy 请看我刚刚做的修改,我认为它将澄清我们一直在说的内容,并表明在这个例子中顺序受到影响,你是正确的。 - vane
显示剩余3条评论

1

其实并没有什么区别,只是一种风格选择。

在你的例子中,如果你选择第二种方法,就不需要提供构造函数,这样可以节省几行代码。


你可能仍然需要一个构造函数。 - JonH
在某些边缘情况下,存在一些细微差异(而不是没有差异)。它们大多数情况下是相同的,尽管每个都有时可能会有所益处。 - Servy
1
@Servy,您能提供一些“特定边缘情况”的示例或指导吗?特别是它们之间的区别。对我来说,它们似乎只是稍微容易维护一些。我真的很想知道这个答案。 - Brad Boyce
1
@BradBoyce 调用基本构造函数就是一个例子。我相信初始化程序在调用基本构造函数之前运行,构造函数在之后运行。 - CodesInChaos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接