引言:为什么自动属性问题吸引了数千次浏览?
\n\n如果你曾经写过 C# 代码,那么你很可能遇到过这样一个简单但重要的问题:"如何正确地为自动属性设置初始值?"。这个问题在 Stack Overflow 上吸引了数万次浏览,这并非没有原因。看似简单的操作背后隐藏着许多陷阱。
\n\n想象一下:你创建了一个 User 类,希望 Name 属性的默认值不是 null,而是一个空字符串。看起来能出什么问题?但在实践中,开发者常常犯下 C# 属性初始化的典型错误,导致 NullReferenceException 或难以理解的行为。
在本文中,我们将分析 TOP-5 错误,并展示如何避免它们。你将了解从 C# 6 到 C# 12 的现代方法,并明白哪种方式最适合你的项目。
\n\n\n\n
错误 #1:忽略自动属性的初始化
\n\n最常见的错误是 不为引用类型设置初始值。许多新手会这样写:
\n\npublic class User\n{\n public string Name { get; set; } // 默认为 null\n public List<string> Roles { get; set; } // 默认为 null\n}\n\n在 C# 中,引用类型的自动属性默认值为 null。这会导致 NullReferenceException 问题,当你忘记在使用前赋值时就会发生。
如何修复?
\n始终显式设置 C# 自动属性初始化器。最简单的方法是使用 C# 6 的语法:
\n\npublic class User\n{\n public string Name { get; set; } = string.Empty;\n public List<string> Roles { get; set; } = new List<string>();\n}\n\n\n\n\n建议:使用
\nstring.Empty而不是空引号——这能提高可读性,并符合 C# 最佳实践。
\n\n
错误 #2:在构造函数中初始化而不考虑调用链
\n\n另一个常见错误是 仅在构造函数中设置 C# 默认值,而忽略了属性初始化器。这可能导致混淆,尤其是在继承场景中。
\n\npublic class BaseClass\n{\n public int Value { get; set; }\n\n public BaseClass()\n {\n Value = 10;\n }\n}\n\npublic class DerivedClass : BaseClass\n{\n public DerivedClass()\n {\n // Value 已经是 10,但我们可以覆盖它\n }\n}\n\n问题:如果你在 Value 属性上添加了初始化器 并且 在构造函数中保留了赋值操作,那么执行顺序可能会让你惊讶。初始化器在基类构造函数体 之前 执行,但在派生类构造函数调用 之后 执行。
如何避免?
\n选择一种方式:要么用初始化器,要么用构造函数。最好优先使用 C# 自动属性初始化器,因为它们更可预测且更简洁。
\n\npublic class BaseClass\n{\n public int Value { get; set; } = 10; // 单一事实来源\n}\n\n\n\n
错误 #3:使用 readonly 字段代替自动属性
\n\n一些开发者在尝试设置初始值时,放弃使用自动属性,转而使用公共字段:
\n\npublic class Config\n{\n public readonly int MaxRetries = 3; // 错误:破坏了封装性\n}\n\n这是一种不良实践。字段破坏了 封装性——你无法在不更改 API 的情况下为 getter/setter 添加逻辑。此外,在序列化或使用 WPF/Blazor 时,这会产生问题。
\n\n正确方法
\n使用带初始化器的自动属性,或者使用 C# 9 的 init-only 属性:<