繁体   English   中英

在构造函数或声明中初始化类字段?

[英]Initialize class fields in constructor or at declaration?

我最近一直在使用 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()
    {
        // .....
    }
}

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

我的规则:

  1. 不要使用声明中的默认值( nullfalse00.0 ...)进行初始化。
  2. 如果您没有更改字段值的构造函数参数,则首选声明中的初始化。
  3. 如果字段的值因构造函数参数而改变,则将初始化放在构造函数中。
  4. 在你的实践中保持一致(最重要的规则)。

在 C# 中没有关系。 您提供的两个代码示例完全等效。 在第一个示例中,C# 编译器(或者是 CLR?)将构造一个空的构造函数并初始化变量,就像它们在构造函数中一样(Jon Skeet 在下面的评论中解释了这一点的细微差别)。 如果已经有一个构造函数,那么任何“上面”的初始化都将被移到它的顶部。

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

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

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

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

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

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

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

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

有许多不同的情况。

我只需要一个空列表

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

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

或者

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

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

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

如果我们担心性能,那么我只初始化必须完成的工作并将其放在最划算的区域。

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

在 C++ 中,我经常在任何地方都没有初始化并将其移动到 Init() 函数中。 为什么? 好吧,在 C++ 中,如果您正在初始化可能在对象构造过程中抛出异常的内容,那么您将面临内存泄漏。

在 Java 中,带有声明的初始化程序意味着该字段始终以相同的方式初始化,无论使用哪个构造函数(如果您有多个)或您的构造函数的参数(如果它们有参数),尽管构造函数可能随后更改值(如果它不是最终的)。 因此,使用带有声明的初始化程序向读者表明,初始化值是该字段在所有情况下的值,无论使用哪个构造函数,也无论传递给任何构造函数的参数如何。 因此,仅当且始终当所有构造对象的值相同时,才使用带有声明的初始化程序。

C# 的设计表明内联初始化是首选,否则它不会出现在语言中。 任何时候都可以避免代码中不同位置之间的交叉引用,通常情况会更好。

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

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

在这种情况下,“考虑”意味着这样做,除非有充分的理由不这样做。 在静态初始化字段的情况下,一个很好的理由是初始化太复杂而无法内联编码。

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

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

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

正如许多其他人所说,这取决于您的使用情况。 保持简单,不要做任何不必要的事情。

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

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

我通常尝试构造函数除了获取依赖项并用它们初始化相关的实例成员之外什么都不做。 如果你想对你的类进行单元测试,这会让你的生活更轻松。

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

不是您关于最佳实践的问题的直接答案,但重要且相关的复习点是,在泛型类定义的情况下,要么将其留给编译器以使用默认值进行初始化,要么我们必须使用特殊方法来初始化字段到它们的默认值(如果这对于代码可读性是绝对必要的)。

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

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

  • 在声明时初始化类字段

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

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

当初始化值可用并且初始化可以放在一行时,这很有效。 但是,这种形式的初始化由于其简单性而具有局限性。 如果初始化需要一些逻辑(例如,错误处理或用于填充复杂数组的 for 循环),那么简单的赋值是不够的。 实例变量可以在构造函数中初始化,在构造函数中可以使用错误处理或其他逻辑。

来自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) 
    {
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM