C#带私有setter的属性为何支持集合初始化语法?
为什么C#允许这类集合初始化操作?
先看你的代码示例,方便大家理解场景:
static class Program { var x = new TestClass { TestList = { "Peter", "Paul" } }; // x.TestList包含3个字符串元素:"Hi there", "Peter", "Paul" } public class TestClass { public List<string> TestList {get; private set;} public TestClass() { TestList = new List<string>() { "Hi there" }; } }
你好奇的核心点其实是:明明TestList的setter是private的,为啥还能通过对象初始化器给它加元素?而且最终是原有元素加新元素,不是直接替换集合。
这其实是C#集合初始化器语法的一种实用设计,我给你拆解清楚:
1. 集合初始化器的两种使用场景
C#的集合初始化器不是只能和new搭配创建新集合,它还有另一种关键用法:对已存在的集合实例追加元素。
- 第一种(创建新集合):
new List<string> { "a", "b" }—— 编译器先创建List<string>实例,再依次调用Add方法把元素加进去。 - 第二种(操作已有集合):
TestList = { "Peter", "Paul" }—— 编译器不会尝试给TestList赋值(哪怕setter是public的也不会),而是先通过getter拿到已有的集合实例,再逐个调用Add方法追加元素。
2. 你的代码被编译器翻译成了啥
编译器会把你写的对象初始化器代码,转换成下面的等价逻辑:
var temp = new TestClass(); // 先调用构造函数,此时TestList已经有"Hi there" temp.TestList.Add("Peter"); // 调用集合的Add方法 temp.TestList.Add("Paul"); var x = temp;
因为TestList的setter是private的,编译器不会傻到去执行temp.TestList = new List<string> { ... }这种赋值操作,自动切换到“调用Add方法”的逻辑。哪怕setter是public的,这种写法也不会替换原有集合——如果要替换,你得明确写TestList = new List<string> { "Peter", "Paul" }。
3. 底层的语言规范逻辑
C#语言规范里明确了这个行为:当对象初始化器中的成员是实现了IEnumerable接口、且包含合适Add方法的类型时,集合初始化器里的元素会被逐个传给该成员的Add方法,而不是对成员本身赋值。
踩坑提醒
如果TestClass的构造函数没初始化TestList(比如TestList是null),这种写法会直接抛出NullReferenceException——因为编译器会尝试调用null对象的Add方法,这显然行不通。
内容的提问来源于stack exchange,提问作者Troncho
相关产品推荐
相关产品推荐

