关于c#:在构造函数或声明中初始化类字段?

Initialize class fields in constructor or at declaration?

我最近一直在用C语言和Java编程,我想知道最适合的地方是初始化我的类字段。

我应该在申报时做吗?:

1
2
3
4
5
6
7
8
9
10
public class Dice
{
    private int topFace = 1;
    private Random myRand = new Random();

    public void Roll()
    {
       // ......
    }
}

还是在一个建设者中?:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Dice
{
    private int topFace;
    private Random myRand;

    public Dice()
    {
        topFace = 1;
        myRand = new Random();
    }

    public void Roll()
    {
        // .....
    }
}

我很好奇你们中的一些退伍军人认为什么是最佳实践。我想保持一致,坚持一种方法。


我的规则:

  • 不要用声明中的默认值初始化(nullfalse00.0…)。
  • 如果没有更改字段值的构造函数参数,则首选在声明中初始化。
  • 如果字段值因构造函数参数而更改,则将初始化放入构造函数中。
  • 在实践中保持一致(最重要的规则)。

  • 在C中,这并不重要。您提供的两个代码示例是完全等效的。在第一个例子中,C编译器(或者它是clr?)将构造一个空的构造函数并初始化变量,就像它们在构造函数中一样(jon skeet在下面的注释中解释了这一点)。如果已经有一个构造函数,那么任何初始化"上面"都将被移到它的顶部。

    就最佳实践而言,前者比后者更不容易出错,因为有人可以轻松地添加另一个构造函数并忘记将其链接起来。


    C语言的语义与Java略有不同。在C中,在调用超类构造函数之前执行声明中的赋值。在Java中,它是在"允许"这个词之后立即使用的(特别适用于匿名内部类),这意味着这两个表单的语义确实匹配。

    如果可以,将字段设为最终字段。


    我认为有一个警告。我曾经犯过这样一个错误:在派生类内部,我试图"在声明时初始化"从抽象基类继承的字段。结果是有两组字段,一组是"基",另一组是新声明的字段,调试花费了我相当长的时间。

    教训:要初始化继承的字段,可以在构造函数内部进行。


    假设您的示例中的类型,那么肯定更喜欢初始化构造函数中的字段。例外情况包括:

    • 静态类/方法中的字段
    • 字段类型为静态/最终/等

    我总是把列在类顶部的字段看作目录(这里包含什么,而不是它是如何使用的),而把构造函数看作引言。当然,方法是章节。


    如果我告诉你,那要看情况而定?

    一般来说,我初始化每件事情并以一致的方式进行。是的,它过于明确,但维护起来也有点容易。

    如果我们担心性能,那么我只初始化必须做的事情,并将其放在最能带来回报的地方。

    在实时系统中,我甚至质疑是否需要变量或常量。

    在C++中,我通常在任何地方不进行初始化,并将其移动到一个In()函数中。为什么?在C++中,如果你正在初始化一些可以在对象构造过程中抛出异常的东西,那么你就会打开内存泄漏。


    在Java中,带有声明的初始化器意味着字段总是以相同的方式初始化,不管使用哪一个构造函数(如果您有多个构造函数)或构造函数的参数(如果它们有参数),尽管构造函数可能随后改变值(如果不是最终的)。因此,使用带有声明的初始值设定项向读者建议,初始化值是字段在所有情况下都具有的值,不管使用的是哪个构造函数,也不管传递给任何构造函数的参数如何。因此,只有当且始终当所有构造对象的值相同时,才将初始值设定项与声明一起使用。


    有许多不同的情况。

    我只需要一张空名单

    情况很清楚。我只需要准备我的列表,并防止在有人向列表中添加项目时引发异常。

    1
    2
    3
    4
    5
    6
    7
    8
    public class CsvFile
    {
        private List<CsvRow> lines = new List<CsvRow>();

        public CsvFile()
        {
        }
    }

    我知道价值观

    我完全知道默认情况下我想要什么值,或者我需要使用其他逻辑。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class AdminTeam
    {
        private List<string> usernames;

        public AdminTeam()
        {
             usernames = new List<string>() {"usernameA","usernameB"};
        }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class AdminTeam
    {
        private List<string> usernames;

        public AdminTeam()
        {
             usernames = GetDefaultUsers(2);
        }
    }

    带有可能值的空列表

    有时,我希望默认情况下有一个空列表,可以通过另一个构造函数添加值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class AdminTeam
    {
        private List<string> usernames = new List<string>();

        public AdminTeam()
        {
        }

        public AdminTeam(List<string> admins)
        {
             admins.ForEach(x => usernames.Add(x));
        }
    }

    考虑到有多个构造函数的情况。对于不同的构造函数,初始化会不同吗?如果它们相同,那么为什么要对每个构造函数重复呢?这与Kokos语句一致,但可能与参数无关。例如,假设您希望保留一个显示对象创建方式的标志。然后,无论构造函数参数如何,该标志都将以不同的方式为不同的构造函数初始化。另一方面,如果对每个构造函数重复相同的初始化,则可能会(无意中)更改某些构造函数中的初始化参数,而不是其他构造函数中的参数。所以,这里的基本概念是,公共代码应该有一个公共位置,并且不可能在不同的位置重复。所以我要说的是,一直把它放在声明中,直到你有了一个不再适合你的特定情况。


    保持一致是很重要的,但这是你要问自己的问题:"我还有其他的构造函数吗?"

    通常,我正在为数据传输创建模型,类本身除了作为变量的容器外什么都不做。

    在这些场景中,我通常没有任何方法或构造函数。我觉得创建一个专门用于初始化列表的构造函数是愚蠢的,特别是因为我可以按照声明初始化它们。

    正如许多人所说,这取决于你的用法。保持简单,不要做任何你不需要做的额外的事情。


    C的设计建议首选内联初始化,否则将不使用该语言。每当您可以避免代码中不同位置之间的交叉引用时,通常情况下都会更好。

    还有与静态字段初始化的一致性问题,静态字段初始化需要内联才能获得最佳性能。建造师设计的框架设计指南说:

    ? CONSIDER initializing static fields inline rather than explicitly using static constructors, because the runtime is able to optimize the performance of types that don’t have an explicitly defined static constructor.

    在这种情况下,"考虑"是指这样做,除非有充分的理由不这么做。在静态初始值设定项字段的情况下,如果初始化太复杂而无法进行内联编码,则是一个很好的原因。


    在声明中设置值有轻微的性能优势。如果您在构造函数中设置它,那么它实际上被设置了两次(首先设置为默认值,然后在ctor中重置)。


    不是对最佳实践问题的直接回答,但重要的和相关的刷新点是,对于一般类定义,要么让编译器使用默认值进行初始化,要么我们必须使用特殊方法将字段初始化为其默认值(如果这对于代码readabilit是绝对必要的)Y)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class MyGeneric<T>
    {
        T data;
        //T data =""; // <-- ERROR
        //T data = 0; // <-- ERROR
        //T data = null; // <-- ERROR        

        public MyGeneric()
        {
            // All of the above errors would be errors here in constructor as well
        }
    }

    将通用字段初始化为其默认值的特殊方法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class MyGeneric<T>
    {
        T data = default(T);

        public MyGeneric()
        {          
            // The same method can be used here in constructor
        }
    }

    我通常只尝试使用构造函数来获取依赖项并用它们初始化相关的实例成员。如果您想对类进行单元测试,这将使您的生活更轻松。

    如果要分配给实例变量的值不受要传递给构造函数的任何参数的影响,则在声明时分配它。