我最近一直在用c#和Java编程,我很好奇初始化我的类字段的最佳位置在哪里。

我应该在申报时申报吗?:

public class Dice
{
    private int topFace = 1;
    private Random myRand = new Random();

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

或者在构造函数中?:

public class Dice
{
    private int topFace;
    private Random myRand;

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

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

我很好奇你们这些老兵认为最好的做法是什么。我想保持一致,坚持一种方法。


我的规则:

不要初始化声明中的默认值(null, false, 0, 0.0…) 如果没有改变字段值的构造函数形参,最好在声明中初始化。 如果字段的值因构造函数形参而改变,则将初始化放在构造函数中。 坚持练习(这是最重要的规则)。


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

静态类/方法中的字段 字段类型为static/final/et al

我总是认为类顶部的字段列表是目录(这里包含什么,而不是如何使用它),构造函数是介绍。方法当然是章节。


如果我告诉你,这要看情况?

我通常初始化所有东西,并以一致的方式进行。是的,它过于明确,但它也更容易维护。

如果我们担心性能,那么我只初始化必须要做的事情,并将其放在性价比最高的地方。

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

在c++中,我经常在这两个地方都不初始化,而是将它移动到Init()函数中。为什么?好吧,在c++中,如果你初始化的东西可能会在对象构造过程中抛出异常,你就会导致内存泄漏。


我通常尝试构造函数不做任何事情,只是获取依赖项并初始化相关的实例成员。如果您想对类进行单元测试,这将使您的工作更加轻松。

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


在c#中,这并不重要。您给出的两个代码示例完全相同。在第一个例子中,c#编译器(或者是CLR?)将构造一个空构造函数并初始化变量,就像它们在构造函数中一样(Jon Skeet在下面的评论中解释了其中的细微差别)。 如果已经有一个构造函数,那么任何“上面”的初始化都将被移动到它的顶部。

就最佳实践而言,前者比后者更不容易出错,因为有人可能很容易添加另一个构造函数而忘记链接它。


在声明中设置值会略微提高性能。如果你在构造函数中设置它,它实际上被设置了两次(第一次为默认值,然后在ctor中重置)。


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

如果可以的话,让这些字段成为最终结果。


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

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


有许多不同的情况。

我只需要一个空列表

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

public class CsvFile
{
    private List<CsvRow> lines = new List<CsvRow>();

    public CsvFile()
    {
    }
}

我知道价值观

我确切地知道在默认情况下我想要什么值,或者我需要使用一些其他逻辑。

public class AdminTeam
{
    private List<string> usernames;

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

or

public class AdminTeam
{
    private List<string> usernames;

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

可能值的空列表

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

public class AdminTeam
{
    private List<string> usernames = new List<string>();

    public AdminTeam()
    {
    }

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

In Java, an initializer with the declaration means the field is always initialized the same way, regardless of which constructor is used (if you have more than one) or the parameters of your constructors (if they have arguments), although a constructor might subsequently change the value (if it is not final). So using an initializer with a declaration suggests to a reader that the initialized value is the value that the field has in all cases, regardless of which constructor is used and regardless of the parameters passed to any constructor. Therefore use an initializer with the declaration only if, and always if, the value for all constructed objects is the same.


c#的设计表明,内联初始化是首选,否则就不会出现在语言中。只要可以避免代码中不同位置之间的交叉引用,通常情况下就会更好。

还有与静态字段初始化的一致性问题,需要内联以获得最佳性能。构造函数设计框架设计指南是这样说的:

考虑内联初始化静态字段,而不是显式使用静态构造函数,因为运行时能够优化没有显式定义静态构造函数的类型的性能。

“考虑”在这里的意思是除非有很好的理由不这样做。对于静态初始化字段,一个很好的理由是初始化太复杂,不能内联编码。


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

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

在这些场景中,我通常没有任何方法或构造函数。对我来说,只为了初始化列表而创建构造函数会感觉很愚蠢,特别是因为我可以在声明中对它们进行内联初始化。

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


Consider the situation where you have more than one constructor. Will the initialization be different for the different constructors? If they will be the same, then why repeat for each constructor? This is in line with kokos statement, but may not be related to parameters. Let's say, for example, you want to keep a flag which shows how the object was created. Then that flag would be initialized differently for different constructors regardless of the constructor parameters. On the other hand, if you repeat the same initialization for each constructor you leave the possibility that you (unintentionally) change the initialization parameter in some of the constructors but not in others. So, the basic concept here is that common code should have a common location and not be potentially repeated in different locations. So I would say always put it in the declaration until you have a specific situation where that no longer works for you.


这不是对你关于最佳实践的问题的直接回答,但一个重要的和相关的复习点是,在泛型类定义的情况下,要么让编译器使用默认值初始化它,要么我们必须使用一个特殊的方法将字段初始化为它们的默认值(如果这对代码可读性绝对必要的话)。

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
    }
}

将泛型字段初始化为默认值的特殊方法如下:

class MyGeneric<T>
{
    T data = default(T);

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

当你不需要一些逻辑或错误处理时:

在声明时初始化类字段

当你需要一些逻辑或错误处理时:

在构造函数中初始化类字段

当初始化值可用时,这工作得很好 初始化可以放在一行上。然而,这种形式 初始化因为简单而有局限性。如果 初始化需要一些逻辑(例如,错误处理或 对于循环填充复杂数组),简单的赋值是不够的。 实例变量可以在构造函数中初始化,其中error 可以使用处理或其他逻辑。

来自https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html。


“更喜欢在声明中初始化”,似乎是一个很好的一般实践。

这里是一个不能在声明中初始化的例子,所以它必须在构造函数中完成。 错误CS0236字段初始化器不能引用非静态字段、方法或属性

class UserViewModel
{
    // Cannot be set here
    public ICommand UpdateCommad { get; private set; }

    public UserViewModel()
    {
        UpdateCommad = new GenericCommand(Update_Method); // <== THIS WORKS
    }

    void Update_Method(object? parameter) 
    {
    }
}