目 录CONTENT

文章目录

CSharp(三十三)对象初始化器(Object Initializer)详解

C# 对象初始化器(Object Initializer)详解


目录

  1. 什么是对象初始化器
  2. 为什么需要对象初始化器
  3. 基本语法
  4. 一步步写出你的第一个对象初始化器
  5. 对象初始化器的工作原理
  6. 嵌套对象初始化
  7. 集合初始化器
  8. 字典初始化器
  9. var 关键字与隐式类型
  10. 与构造函数的配合使用
  11. 只读属性的初始化
  12. 匿名类型(匿名对象)
  13. 对象初始化器 vs 传统方式对照表
  14. 实战案例:订单系统
  15. 实战案例:配置文件对象
  16. 实战案例:构建复杂报表
  17. 常见错误与注意事项
  18. 面试常考题
  19. 课后练习
  20. 小结

1. 什么是对象初始化器

一个生活的比喻

想象你去奶茶店点一杯奶茶:

传统方式(一句一句说):

你:"我要一杯奶茶。"
店员:"好的。"(递给你一杯空杯)
你:"加珍珠。"
店员:(加珍珠)
你:"加椰果。"
店员:(加椰果)
你:"少糖。"
店员:(少放糖)
你:"去冰。"
店员:(去冰)

初始化器方式(一口气说完):

你:"我要一杯奶茶,加珍珠、加椰果、少糖、去冰。"
店员:(一次性做好给你)

在 C# 里,对象初始化器就是让你在 new 一个对象的同时,一次性把属性值都设好,不用一句一句地赋值。

一句话总结:对象初始化器让你在创建对象的同时,用 { } 大括号直接给属性赋值,代码更紧凑、更清晰。


2. 为什么需要对象初始化器

传统方式的痛点

// 传统方式:创建对象 + 逐行赋值
Book book = new Book();
book.Title = "C# 编程指南";
book.Author = "张三";
book.Pages = 500;
book.Price = 79.9m;

这种写法有几个问题:

  • 啰嗦book. 出现了 5 次
  • 分散:属性和值的对应关系不够直观
  • 容易漏:可能忘了给某个属性赋值

对象初始化器的优势

// 对象初始化器:一口气完成
Book book = new Book
{
    Title = "C# 编程指南",
    Author = "张三",
    Pages = 500,
    Price = 79.9m
};
  • 紧凑:创建和赋值在同一个代码块里
  • 直观:一眼就能看出这个对象有哪些值
  • 安全:编译器帮你检查语法,不会漏括号

记忆口诀:传统方式是"先出生,后打扮";初始化器是"出生时就打扮好"。


3. 基本语法

语法格式

类型 变量名 = new 类型
{
    属性1 = 值1,
    属性2 = 值2,
    属性3 = 值3
    // 注意:最后一个属性后面可以加逗号,也可以不加
};

关键规则

规则 说明
使用 { } 包裹 大括号跟在 new 类型 后面
属性 = 值 = 赋值,不是 :
逗号分隔 每个属性赋值后加逗号
分号结尾 整个 new 语句最后加 ;
顺序任意 属性的赋值顺序可以和类中声明顺序不同
可选属性 不需要给所有属性赋值,只写你需要的

可视化语法

           new 关键字    类型名      开始大括号
              │          │            │
              ▼          ▼            ▼
var book = new Book
{                               ← 属性赋值列表
    Title = "C#入门",            ← 属性1 = 值1,
    Author = "张三",             ← 属性2 = 值2,
    Price = 59.9m               ← 属性3 = 值3(最后一项逗号可选)
};                              ← 结束大括号 + 分号

4. 一步步写出你的第一个对象初始化器

场景:创建一个"学生"对象

using System;

// 第1步:定义一个学生类
public class Student
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Grade { get; set; }
    public double Score { get; set; }

    // 方便输出
    public override string ToString()
    {
        return $"{Name}, {Age}岁, {Grade}年级, 成绩:{Score}";
    }
}

// 第2步:用对象初始化器创建对象
class Program
{
    static void Main()
    {
        // 一口气创建并赋值
        Student s1 = new Student
        {
            Name = "张三",
            Age = 18,
            Grade = "高三",
            Score = 95.5
        };

        Console.WriteLine(s1);
        // 输出:张三, 18岁, 高三年级, 成绩:95.5

        // 可以只给部分属性赋值,其他用默认值
        Student s2 = new Student
        {
            Name = "李四",
            Score = 88
        };
        // Age = 0(默认值), Grade = null(默认值)

        Console.WriteLine(s2);
        // 输出:李四, 0岁, 年级, 成绩:88
    }
}

对比:三种写法

// 写法1:传统方式(最啰嗦)
Student s1 = new Student();
s1.Name = "张三";
s1.Age = 18;
s1.Grade = "高三";
s1.Score = 95.5;

// 写法2:对象初始化器(推荐)
Student s2 = new Student
{
    Name = "张三",
    Age = 18,
    Grade = "高三",
    Score = 95.5
};

// 写法3:构造函数(如果有合适的构造参数)
Student s3 = new Student("张三", 18, "高三", 95.5);
// 需要提前写构造函数,不够灵活

5. 对象初始化器的工作原理

编译器做了什么?

// 你写的代码
Student s = new Student
{
    Name = "张三",
    Age = 18
};

// 编译器实际生成的等价代码
Student temp = new Student();   // 1. 先 new 一个对象(无参构造函数)
temp.Name = "张三";              // 2. 逐行赋值
temp.Age = 18;                   // 3. 逐行赋值
Student s = temp;                // 4. 最后赋值给变量

执行流程图

new Student { Name="张三", Age=18 }
       │
       ▼
  ┌─────────────────────┐
  │ 1. new Student()    │  ← 先调用构造函数,创建对象
  │    生成临时对象 temp  │
  └────────┬────────────┘
           │
           ▼
  ┌─────────────────────┐
  │ 2. temp.Name="张三"  │  ← 按你写的顺序,逐行赋值
  │ 3. temp.Age=18      │
  └────────┬────────────┘
           │
           ▼
  ┌─────────────────────┐
  │ 4. Student s = temp │  ← 把初始化好的对象赋给变量
  └─────────────────────┘

重要:对象初始化器必须先调用构造函数,然后再设置属性。它不做任何"黑魔法",只是语法糖(让代码更好看的写法)。

验证:构造函数先执行

public class Student
{
    public string Name { get; set; }
    public int Age { get; set; }

    public Student()
    {
        Console.WriteLine("构造函数执行了!");
    }
}

// 使用初始化器
Student s = new Student { Name = "张三", Age = 18 };

// 输出:
//   构造函数执行了!
// (说明:先执行构造函数,再设置属性)

6. 嵌套对象初始化

一个对象里面可能包含另一个对象,初始化器支持嵌套写法:

public class Author
{
    public string Name { get; set; }
    public string Email { get; set; }
}

public class Publisher
{
    public string Name { get; set; }
    public string City { get; set; }
}

public class Book
{
    public string Title { get; set; }
    public decimal Price { get; set; }
    public Author Author { get; set; }        // 嵌套对象
    public Publisher Publisher { get; set; }  // 嵌套对象
    public List<string> Tags { get; set; }    // 嵌套集合
}

// ===== 嵌套初始化 =====
Book book = new Book
{
    Title = "深入理解C#",
    Price = 99.0m,

    // 嵌套初始化 Author
    Author = new Author
    {
        Name = "李四",
        Email = "lisi@example.com"
    },

    // 嵌套初始化 Publisher
    Publisher = new Publisher
    {
        Name = "教育出版社",
        City = "北京"
    },

    // 嵌套初始化 Tags(集合)
    Tags = new List<string>
    {
        "C#",
        "编程",
        ".NET",
        "入门"
    }
};

// 访问嵌套属性
Console.WriteLine(book.Author.Name);      // 输出:李四
Console.WriteLine(book.Publisher.City);   // 输出:北京
Console.WriteLine(book.Tags[0]);          // 输出:C#

嵌套结构的可视化

new Book
{
    Title = "深入理解C#",
    Price = 99.0m,

    Author = new Author           ← 嵌套一层
    {
        Name = "李四",
        Email = "lisi@example.com"
    },

    Publisher = new Publisher     ← 再嵌套一层
    {
        Name = "教育出版社",
        City = "北京"
    },

    Tags = new List<string>       ← 嵌套集合
    {
        "C#", "编程", ".NET"
    }
}

7. 集合初始化器

C# 允许你像初始化对象一样初始化集合(List、数组等):

List 集合初始化

using System.Collections.Generic;

// 传统方式
List<string> names1 = new List<string>();
names1.Add("张三");
names1.Add("李四");
names1.Add("王五");

// 集合初始化器(推荐)
List<string> names2 = new List<string>
{
    "张三",
    "李四",
    "王五"
};

// 包含对象的集合
List<Student> students = new List<Student>
{
    new Student { Name = "张三", Age = 18, Score = 95 },
    new Student { Name = "李四", Age = 19, Score = 88 },
    new Student { Name = "王五", Age = 17, Score = 92 }
};

// 遍历
foreach (var s in students)
{
    Console.WriteLine($"{s.Name}: {s.Score}分");
}

数组初始化

// 传统方式
int[] arr1 = new int[3];
arr1[0] = 10;
arr1[1] = 20;
arr1[2] = 30;

// 数组初始化器(简化写法)
int[] arr2 = new int[] { 10, 20, 30 };

// 更简化的写法
int[] arr3 = { 10, 20, 30 };

// 对象数组
Student[] studentArr = new Student[]
{
    new Student { Name = "张三", Age = 18 },
    new Student { Name = "李四", Age = 19 }
};

自定义集合也支持

只要你的集合类有 Add 方法,就能用集合初始化器:

public class MyCollection
{
    private List<string> items = new List<string>();

    // 只要有 Add 方法,就能用集合初始化器
    public void Add(string item)
    {
        items.Add(item);
    }

    // 也支持多参数 Add
    public void Add(string key, string value)
    {
        items.Add($"{key}:{value}");
    }
}

// 使用
MyCollection mc = new MyCollection
{
    "张三",                    // 调用 Add("张三")
    "李四",                    // 调用 Add("李四")
    { "Name", "王五" }         // 调用 Add("Name", "王五")
};

8. 字典初始化器

字典(Dictionary)的初始化有两种写法:

方式1:索引初始化器(C# 6.0+,推荐)

using System.Collections.Generic;

Dictionary<string, int> scores = new Dictionary<string, int>
{
    ["张三"] = 95,
    ["李四"] = 88,
    ["王五"] = 92
};

// 等价于:
// scores["张三"] = 95;
// scores["李四"] = 88;
// scores["王五"] = 92;

方式2:传统字典初始化器(兼容旧版本)

Dictionary<string, string> countries = new Dictionary<string, string>
{
    { "CN", "中国" },
    { "US", "美国" },
    { "JP", "日本" },
    { "KR", "韩国" }
};

// 等价于:
// countries.Add("CN", "中国");
// countries.Add("US", "美国");
// ...

两种方式对比

特性 索引初始化器 ["key"] = value 传统初始化器 { key, value }
C# 版本 6.0+ 3.0+
可读性 更直观 稍弱
重复键 覆盖旧值(用索引器) 抛异常(用 Add)
推荐度 ✅ 推荐 旧代码中常见

复杂字典示例

// 字典的值是对象
Dictionary<int, Student> studentDict = new Dictionary<int, Student>
{
    [1001] = new Student { Name = "张三", Age = 18 },
    [1002] = new Student { Name = "李四", Age = 19 },
    [1003] = new Student { Name = "王五", Age = 17 }
};

// 使用
Student s = studentDict[1001];
Console.WriteLine(s.Name);  // 输出:张三

// 嵌套字典
Dictionary<string, Dictionary<string, int>> grades = new Dictionary<string, Dictionary<string, int>>
{
    ["张三"] = new Dictionary<string, int>
    {
        ["语文"] = 90,
        ["数学"] = 95,
        ["英语"] = 88
    },
    ["李四"] = new Dictionary<string, int>
    {
        ["语文"] = 85,
        ["数学"] = 92
    }
};

Console.WriteLine(grades["张三"]["数学"]);  // 输出:95

9. var 关键字与隐式类型

对象初始化器常常和 var 关键字一起使用:

var 是什么?

var 让编译器自动推断变量的类型。注意:这不是动态类型,类型在编译时就确定了。

// 显式类型:你告诉编译器类型
Book book1 = new Book { Title = "C#入门" };

// var:编译器自己看出类型
var book2 = new Book { Title = "C#入门" };  // 编译后,book2 的类型还是 Book

// 更多例子
var name = "张三";       // 编译器推断为 string
var age = 18;            // 编译器推断为 int
var price = 59.9m;       // 编译器推断为 decimal
var list = new List<int> { 1, 2, 3 };  // 编译器推断为 List<int>

什么时候用 var?

// ✅ 推荐用 var:类型名很明显(右边有 new)
var book = new Book { Title = "C#入门" };
var dict = new Dictionary<string, int>();
var students = new List<Student>();

// ✅ 推荐用 var:类型名很长很复杂
var result = new Dictionary<string, List<Dictionary<int, Student>>>();

// ⚠️ 不推荐用 var:类型不明显
var x = GetSomething();  // GetSomething 返回什么?看不出来

// ❌ 不能用 var:字段、方法参数、返回值
class X
{
    // var field = 1;              // ❌ 字段不能用 var
    // void M(var x) { }           // ❌ 参数不能用 var
    // var M() { return 1; }       // ❌ 返回值不能用 var
}

经验法则:当 new 右边明确写了类型时,左边用 var 可以减少重复。其他情况用显式类型更清晰。


10. 与构造函数的配合使用

对象初始化器可以和构造函数同时使用,结合起来非常强大:

public class Product
{
    // 只读属性
    public string Id { get; }
    public DateTime CreatedTime { get; }

    // 可读写属性
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Category { get; set; }

    // 构造函数:负责初始化只读属性和必填字段
    public Product(string id, string name, decimal price)
    {
        Id = id;
        Name = name;
        Price = price;
        CreatedTime = DateTime.Now;
    }
}

// ===== 构造函数 + 对象初始化器 =====
// 构造函数处理必填字段,初始化器处理可选字段
Product p = new Product("P001", "笔记本电脑", 5999m)
{
    Category = "电子产品"   // 可选字段用初始化器
};

Console.WriteLine($"{p.Id}: {p.Name}, {p.Category}, {p.Price}");
// 输出:P001: 笔记本电脑, 电子产品, 5999

最佳实践:分工明确

┌─────────────────────────────────────────┐
│             创建对象时                    │
├─────────────────────────────────────────┤
│  构造函数参数  →  处理必须有的数据        │
│                  (如:ID、姓名等必填项)  │
│                                          │
│  对象初始化器  →  处理可选的数据           │
│                  (如:备注、分类等可选项) │
└─────────────────────────────────────────┘
// ✅ 好的设计:必填项走构造函数,可选项走初始化器
var order = new Order(customerId: "C001", orderDate: DateTime.Today)
{
    Notes = "请放快递柜",
    IsGift = true,
    GiftMessage = "生日快乐!"
};

// ❌ 不好的设计:全部用初始化器,可能漏掉必填项
var order = new Order
{
    CustomerId = "C001",          // 如果忘了这个,订单没有归属!
    OrderDate = DateTime.Today,   // 如果忘了这个,订单日期为空!
    Notes = "请放快递柜"
};

11. 只读属性的初始化

只读属性(只有 get,没有 set)也可以在初始化器中赋值:

public class Config
{
    // 只读属性:只有 get
    public string AppName { get; }
    public string Version { get; }
    public int MaxRetry { get; }

    // 可读写属性
    public string Description { get; set; }
}

// ===== 在初始化器中给只读属性赋值 =====
// 只能在以下两个地方赋值:
//   1. 构造函数
//   2. 对象初始化器
Config config = new Config
{
    AppName = "MyApp",
    Version = "1.0.0",
    MaxRetry = 3,
    Description = "这是一个示例配置"
};

Console.WriteLine(config.AppName);  // 输出:MyApp

// config.AppName = "NewApp";       // ❌ 编译错误!只读属性不能修改

注意:只读属性在初始化器中赋值后,就不能再修改了。这提供了一种创建后即不可变的数据保护方式。


12. 匿名类型(匿名对象)

有时候你只需要临时用一组数据,不想专门定义一个类。这时可以用匿名类型

// 匿名类型:不用事先定义类,直接写属性
var person = new
{
    Name = "张三",
    Age = 18,
    City = "北京"
};

// 使用:和普通对象一样
Console.WriteLine($"姓名:{person.Name}");  // 输出:姓名:张三
Console.WriteLine($"年龄:{person.Age}");   // 输出:年龄:18
Console.WriteLine($"城市:{person.City}");  // 输出:城市:北京

匿名类型的规则

// 规则1:属性是只读的
var p = new { Name = "张三", Age = 18 };
// p.Name = "李四";   // ❌ 编译错误!匿名类型属性只读

// 规则2:同名同序的匿名类型共享一个类型
var a = new { X = 1, Y = 2 };
var b = new { X = 3, Y = 4 };
// a 和 b 是同一个匿名类型!

// 规则3:不同属性名或不同顺序 = 不同类型
var c = new { X = 1, Y = 2 };  // 类型:{ int X, int Y }
var d = new { Y = 2, X = 1 };  // 类型:{ int Y, int X } → 不同类型!

// 规则4:可以根据变量名自动推断属性名
string name = "张三";
int age = 18;
var p2 = new { name, age };  // 等价于 new { name = name, age = age }
Console.WriteLine(p2.name);  // 输出:张三

典型使用场景:LINQ 查询

// 从学生列表里选出部分字段
var students = new List<Student>
{
    new Student { Name = "张三", Age = 18, Score = 95 },
    new Student { Name = "李四", Age = 19, Score = 88 }
};

// 只取 Name 和 Score,不要 Age(用匿名类型)
var result = students.Select(s => new
{
    s.Name,
    s.Score,
    Grade = s.Score >= 90 ? "优秀" : "良好"
});

foreach (var item in result)
{
    Console.WriteLine($"{item.Name}: {item.Score}分 ({item.Grade})");
}
// 输出:
//   张三: 95分 (优秀)
//   李四: 88分 (良好)

13. 对象初始化器 vs 传统方式对照表

特性 传统方式 对象初始化器
代码行数 多(每属性一行) 少(集中在一个块内)
可读性 分散 集中,一目了然
漏赋值风险 容易漏 不容易漏(一眼能看出)
临时变量 需要 不需要(编译器用临时变量)
只读属性 只能在构造函数赋值 可以在初始化器中赋值
嵌套对象 多层嵌套很啰嗦 嵌套结构清晰
与构造函数配合 先构造再赋值 同时使用
LINQ 配合 不方便 天生一对

代码量对比

// 传统方式:9 行
Book b1 = new Book();
b1.Title = "C#入门";
b1.Author = "张三";
b1.Pages = 500;
b1.Price = 79.9m;
b1.Publisher = new Publisher();
b1.Publisher.Name = "教育出版社";
b1.Publisher.City = "北京";

// 对象初始化器:7 行(结构更清晰)
Book b2 = new Book
{
    Title = "C#入门",
    Author = "张三",
    Pages = 500,
    Price = 79.9m,
    Publisher = new Publisher { Name = "教育出版社", City = "北京" }
};

14. 实战案例:订单系统

做一个完整订单创建的例子:

using System;
using System.Collections.Generic;

// ==================== 数据类 ====================

public class Customer
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Phone { get; set; }
    public string Address { get; set; }
}

public class OrderItem
{
    public string ProductId { get; set; }
    public string ProductName { get; set; }
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
    public decimal SubTotal => Quantity * UnitPrice;  // 小计
}

public class Order
{
    public string OrderId { get; set; }
    public DateTime OrderTime { get; set; }
    public Customer Customer { get; set; }
    public List<OrderItem> Items { get; set; }
    public string Notes { get; set; }
    public bool IsGift { get; set; }
    public string GiftMessage { get; set; }

    // 总金额
    public decimal TotalAmount
    {
        get
        {
            decimal total = 0;
            foreach (var item in Items)
                total += item.SubTotal;
            return total;
        }
    }
}

// ==================== 使用对象初始化器创建订单 ====================

class Program
{
    static void Main()
    {
        // 一次性创建完整订单!
        Order order = new Order
        {
            OrderId = "ORD" + DateTime.Now.ToString("yyyyMMddHHmmss"),
            OrderTime = DateTime.Now,

            // 嵌套初始化客户信息
            Customer = new Customer
            {
                Id = "C001",
                Name = "张三",
                Phone = "13800138000",
                Address = "北京市朝阳区xx路xx号"
            },

            // 嵌套初始化订单明细
            Items = new List<OrderItem>
            {
                new OrderItem
                {
                    ProductId = "P001",
                    ProductName = "笔记本电脑",
                    Quantity = 1,
                    UnitPrice = 5999m
                },
                new OrderItem
                {
                    ProductId = "P002",
                    ProductName = "鼠标",
                    Quantity = 2,
                    UnitPrice = 99m
                },
                new OrderItem
                {
                    ProductId = "P003",
                    ProductName = "键盘",
                    Quantity = 1,
                    UnitPrice = 299m
                }
            },

            Notes = "请放快递柜",
            IsGift = false
        };

        // ===== 打印订单 =====
        Console.WriteLine("========== 订单信息 ==========");
        Console.WriteLine($"订单号:{order.OrderId}");
        Console.WriteLine($"下单时间:{order.OrderTime:yyyy-MM-dd HH:mm:ss}");
        Console.WriteLine($"客户:{order.Customer.Name}");
        Console.WriteLine($"电话:{order.Customer.Phone}");
        Console.WriteLine($"地址:{order.Customer.Address}");
        Console.WriteLine();
        Console.WriteLine("商品明细:");
        Console.WriteLine("  名称           数量  单价      小计");
        Console.WriteLine("  ────────────────────────────────");

        foreach (var item in order.Items)
        {
            Console.WriteLine($"  {item.ProductName,-10}  {item.Quantity,-4}  {item.UnitPrice,8:C}  {item.SubTotal,8:C}");
        }

        Console.WriteLine("  ────────────────────────────────");
        Console.WriteLine($"  合计:{order.TotalAmount,26:C}");
        Console.WriteLine($"  备注:{order.Notes}");
        Console.WriteLine("===============================");
    }
}

输出效果

========== 订单信息 ==========
订单号:ORD20260626090000
下单时间:2026-06-26 09:00:00
客户:张三
电话:13800138000
地址:北京市朝阳区xx路xx号

商品明细:
  名称           数量  单价      小计
  ────────────────────────────────
  笔记本电脑      1      ¥5,999.00  ¥5,999.00
  鼠标            2         ¥99.00    ¥198.00
  键盘            1        ¥299.00    ¥299.00
  ────────────────────────────────
  合计:                    ¥6,496.00
  备注:请放快递柜
===============================

这个例子展示了对象初始化器在实际项目中的典型用法:一层嵌套一层,清晰地表达复杂数据结构


15. 实战案例:配置文件对象

用对象初始化器来构造配置数据:

using System;
using System.Collections.Generic;

public class AppConfig
{
    public string AppName { get; set; }
    public string Version { get; set; }
    public DatabaseConfig Database { get; set; }
    public LogConfig Logging { get; set; }
    public List<string> AllowedHosts { get; set; }
}

public class DatabaseConfig
{
    public string Server { get; set; }
    public int Port { get; set; }
    public string UserId { get; set; }
    public string Password { get; set; }
    public int MaxPoolSize { get; set; }
}

public class LogConfig
{
    public string Level { get; set; }         // Debug / Info / Warn / Error
    public string OutputPath { get; set; }
    public long MaxFileSizeMB { get; set; }
    public bool Console { get; set; }
}

class Program
{
    static void Main()
    {
        // 用初始化器构建完整配置
        AppConfig config = new AppConfig
        {
            AppName = "订单管理系统",
            Version = "2.1.0",

            Database = new DatabaseConfig
            {
                Server = "192.168.1.100",
                Port = 1433,
                UserId = "admin",
                Password = "********",
                MaxPoolSize = 100
            },

            Logging = new LogConfig
            {
                Level = "Info",
                OutputPath = @"D:\Logs\OrderSystem\",
                MaxFileSizeMB = 10,
                Console = true
            },

            AllowedHosts = new List<string>
            {
                "localhost",
                "192.168.1.0/24",
                "*.example.com"
            }
        };

        // ===== 打印配置 =====
        Console.WriteLine("========== 应用配置 ==========");
        Console.WriteLine($"应用:{config.AppName} v{config.Version}");
        Console.WriteLine();
        Console.WriteLine("数据库:");
        Console.WriteLine($"  服务器:{config.Database.Server}:{config.Database.Port}");
        Console.WriteLine($"  用户:{config.Database.UserId}");
        Console.WriteLine($"  连接池上限:{config.Database.MaxPoolSize}");
        Console.WriteLine();
        Console.WriteLine("日志:");
        Console.WriteLine($"  级别:{config.Logging.Level}");
        Console.WriteLine($"  路径:{config.Logging.OutputPath}");
        Console.WriteLine($"  文件上限:{config.Logging.MaxFileSizeMB}MB");
        Console.WriteLine($"  控制台输出:{config.Logging.Console}");
        Console.WriteLine();
        Console.WriteLine("允许的主机:");
        foreach (var host in config.AllowedHosts)
            Console.WriteLine($"  - {host}");
        Console.WriteLine("==============================");
    }
}

16. 实战案例:构建复杂报表

using System;
using System.Collections.Generic;

public class Report
{
    public string Title { get; set; }
    public string Author { get; set; }
    public DateTime CreateTime { get; set; }
    public List<ReportSection> Sections { get; set; }
}

public class ReportSection
{
    public string Heading { get; set; }
    public string Description { get; set; }
    public List<DataRow> Data { get; set; }
    public string Summary { get; set; }
}

public class DataRow
{
    public string Name { get; set; }
    public decimal Value { get; set; }
    public decimal Percent { get; set; }
    public string Trend { get; set; }  // "↑" / "↓" / "→"
}

class Program
{
    static void Main()
    {
        Report report = new Report
        {
            Title = "2026年第二季度销售报告",
            Author = "张三",
            CreateTime = DateTime.Now,

            Sections = new List<ReportSection>
            {
                // 第一部分:按地区
                new ReportSection
                {
                    Heading = "一、按地区统计",
                    Description = "全国各区域销售情况汇总",
                    Data = new List<DataRow>
                    {
                        new DataRow { Name = "华北", Value = 1250000m, Percent = 28.5m, Trend = "↑" },
                        new DataRow { Name = "华东", Value = 2100000m, Percent = 47.8m, Trend = "↑" },
                        new DataRow { Name = "华南", Value = 680000m,  Percent = 15.5m, Trend = "↓" },
                        new DataRow { Name = "西部", Value = 362000m,  Percent = 8.2m,  Trend = "→" }
                    },
                    Summary = "华东地区占比最高,华南需重点关注"
                },

                // 第二部分:按产品线
                new ReportSection
                {
                    Heading = "二、按产品线统计",
                    Description = "各产品线销售额对比",
                    Data = new List<DataRow>
                    {
                        new DataRow { Name = "电脑",   Value = 1800000m, Percent = 41.0m, Trend = "↑" },
                        new DataRow { Name = "手机",   Value = 1500000m, Percent = 34.1m, Trend = "↑" },
                        new DataRow { Name = "配件",   Value = 800000m,  Percent = 18.2m, Trend = "→" },
                        new DataRow { Name = "服务",   Value = 292000m,  Percent = 6.7m,  Trend = "↓" }
                    },
                    Summary = "电脑和手机仍是主力产品线"
                }
            }
        };

        // ===== 渲染报表 =====
        Console.WriteLine(new string('=', 50));
        Console.WriteLine($"  {report.Title}");
        Console.WriteLine($"  作者:{report.Author}  时间:{report.CreateTime:yyyy-MM-dd}");
        Console.WriteLine(new string('=', 50));

        foreach (var section in report.Sections)
        {
            Console.WriteLine($"\n{section.Heading}");
            Console.WriteLine($"  {section.Description}");
            Console.WriteLine("  -------------------------------------------------");
            Console.WriteLine("  {" + "名称,-8} {" + "销售额,12} {" + "占比,8} {" + "趋势", 4}");
            Console.WriteLine("  -------------------------------------------------");

            foreach (var row in section.Data)
            {
                Console.WriteLine($"  {row.Name,-8} {row.Value,12:N0} {row.Percent,7:P1} {row.Trend,4}");
            }

            Console.WriteLine("  -------------------------------------------------");
            Console.WriteLine($"  小结:{section.Summary}");
        }

        Console.WriteLine($"\n{new string('=', 50)}");
    }
}

17. 常见错误与注意事项

错误1:忘了写分号

// ❌ 编译错误:大括号后面少了分号
Book book = new Book
{
    Title = "C#入门",
    Author = "张三"
}  // 这里缺少 ;

// ✅ 正确
Book book = new Book
{
    Title = "C#入门",
    Author = "张三"
};  // ← 分号不能忘!

错误2:属性和字段混淆

public class Student
{
    public string Name { get; set; }   // 属性 → ✅ 可以在初始化器中赋值
    public int age;                    // 字段 → ⚠️ 可以在初始化器中赋值
    private int score;                 // 私有字段 → ❌ 初始化器无法访问
}

var s = new Student
{
    Name = "张三",   // ✅ 公共属性,可以
    age = 18         // ✅ 公共字段,也可以
    // score = 95    // ❌ 私有字段,不可以
};

错误3:匿名类型试图修改值

var p = new { Name = "张三", Age = 18 };
// p.Name = "李四";   // ❌ 编译错误!匿名类型属性只读

错误4:初始化器中没有构造函数需要的参数

public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }

    // 只有一个有参构造函数
    public Product(string name)
    {
        Name = name;
    }
    // 没有无参构造函数!
}

// ❌ 编译错误!找不到无参构造函数
// var p = new Product { Name = "电脑", Price = 5999m };

// ✅ 正确:提供构造参数 + 初始化器
var p = new Product("电脑") { Price = 5999m };

错误5:在初始化器中调用方法

// ❌ 不能在初始化器里调用方法
var s = new Student
{
    Name = "张三",
    // DoSomething()  // ❌ 初始化器里只能赋值属性/字段
};

错误6:过度嵌套导致可读性下降

// ⚠️ 嵌套太深反而难读
var x = new A
{
    B = new B
    {
        C = new C
        {
            D = new D
            {
                E = new E { Value = 1 }
            }
        }
    }
};

// ✅ 超过3层嵌套建议分步写
var e = new E { Value = 1 };
var d = new D { E = e };
var c = new C { D = d };
// ...

18. 面试常考题

Q1:对象初始化器会先调用构造函数吗?

:是的。对象初始化器的执行顺序是:先调用构造函数创建对象,然后按你写的顺序逐个给属性赋值。

// new Book { Title = "C#" }
// 等价于:
// Book temp = new Book();  ← 先调构造函数
// temp.Title = "C#";        ← 再赋值

Q2:对象初始化器和构造函数各适合什么场景?

场景 使用
必填字段(如 ID) 构造函数参数
可选字段 对象初始化器
创建后不可变的数据 构造函数参数 + 只读属性
灵活配置的数据 对象初始化器

Q3:匿名类型的属性可以修改吗?

:不能。匿名类型的属性是只读的,创建后不能修改。

Q4:集合初始化器对自定义集合有什么要求?

:自定义集合类需要满足两个条件:

  1. 实现 IEnumerable(或 IEnumerable<T>
  2. 有公开的 Add 方法

Q5:var 是动态类型吗?

:不是。var编译时类型推断,变量类型在编译时就完全确定了,和显式声明 intstring 没有区别。这和 JavaScript 的 var、C# 的 dynamic 完全不同。


19. 课后练习

练习1:汽车配置器

用对象初始化器创建一个汽车对象。

// 要求:
// 1. Car 类:品牌、型号、价格、颜色、排量
// 2. Engine 类:类型(燃油/电动)、马力、扭矩
// 3. Car 包含 Engine 对象(嵌套)
// 4. 用对象初始化器一次性创建

// 期望效果:
// Car car = new Car { ... };
// Console.WriteLine($"{car.Brand} {car.Model}, {car.Engine.Type}");
点击查看参考代码
public class Engine
{
    public string Type { get; set; }     // "燃油" / "电动"
    public int Horsepower { get; set; }
    public int Torque { get; set; }
}

public class Car
{
    public string Brand { get; set; }
    public string Model { get; set; }
    public decimal Price { get; set; }
    public string Color { get; set; }
    public double Displacement { get; set; }
    public Engine Engine { get; set; }

    public override string ToString()
        => $"{Brand} {Model}, {Color}, {Engine.Type}发动机, {Engine.Horsepower}马力, ¥{Price:N0}";
}

// 使用
var car = new Car
{
    Brand = "比亚迪",
    Model = "汉",
    Price = 209800m,
    Color = "赤帝红",
    Displacement = 0,
    Engine = new Engine
    {
        Type = "电动",
        Horsepower = 222,
        Torque = 330
    }
};

Console.WriteLine(car);

练习2:学生成绩单

用集合初始化器创建学生列表和成绩字典。

// 要求:
// 1. 用集合初始化器创建 5 个学生的 List
// 2. 用字典初始化器创建学生选课表:
//    key=学生姓名, value=课程列表(List<string>)
// 3. 用嵌套匿名类型打印每个学生的选课数

练习3:API 请求参数

用对象初始化器创建 HTTP 请求配置。

// 要求:
// RequestConfig 类包含:
// 1. Url(string)
// 2. Method(string:"GET"/"POST")
// 3. Headers(Dictionary<string, string>)
// 4. Body(object,可选)
// 5. TimeoutSeconds(int)

// 用对象初始化器构建一个 POST 请求配置

20. 小结

┌────────────────────────────────────────────────────────────┐
│                  对象初始化器核心要点                          │
├────────────────────────────────────────────────────────────┤
│  语法:  new 类型 { 属性1 = 值1, 属性2 = 值2, ... }           │
│                                                            │
│  本质:  语法糖,编译器展开为 "new + 逐行赋值"                  │
│                                                            │
│  执行:  先调构造函数,再逐属性赋值                             │
│                                                            │
│  嵌套:  支持无限层级嵌套(属性里再 new 一个对象)              │
│                                                            │
│  集合:  new List<int> { 1, 2, 3 }                           │
│                                                            │
│  字典:  new Dict<string,int> { ["key"] = value }            │
│                                                            │
│  匿名:  new { Name="张三", Age=18 }(临时用,不定义类)       │
│                                                            │
│  var:  var x = new Book { ... }(让编译器推断类型)           │
│                                                            │
│  场景:  创建复杂对象、配置对象、DTO、LINQ 结果                  │
│                                                            │
│  原则:  必填 → 构造函数 / 可选 → 初始化器                      │
└────────────────────────────────────────────────────────────┘

一句话总结

对象初始化器让你在 new 的同时用 { } 直接给属性赋值,省去逐行赋值的麻烦。配合嵌套、集合、字典初始化器,可以把复杂的数据结构一次性清晰地构建出来。本质是语法糖,但大大提升了代码可读性。


本文档专为教学编写,重在通俗易懂。建议每学完一个概念就动手敲一遍代码,加深理解。

0
博主关闭了当前页面的评论