首页 / C# 入门教程 / 构造函数

C# 入门教程

构造函数

本教程共 100 篇 · 第 31 篇 · 更新于 2026-07-31 · 约 12 分钟阅读

C#C# 入门教程构造函数面向对象

31. 构造函数

本节目标:学完你能写出一个类,并在 new 时正确初始化它的字段和属性。

你用 new 创建一个对象时,总得有人替它把初始状态安顿好。这个人就是构造函数(constructor)。它和类同名,没有返回类型,对象一诞生就自动跑。

构造函数长什么样

构造函数不写返回类型,连 void 都不写。它名字必须和类名一模一样。下面这个 Person 在创建时打印一句话,告诉我们它工作了。

namespace CSharpDemo;

class Person
{
    public string Name;
    public int Age;

    public Person()
    {
        Console.WriteLine("一个新 Person 诞生了");
    }
}

var p = new Person();
Note

构造函数不能被你手动「调用方法」那样点出来,它只在 new 的瞬间由系统触发。

默认构造函数从哪来

如果你一个构造函数都没写,C# 会偷偷送你一个默认构造函数(default constructor):它无参数,把字段设成该类型的零值(int 为 0、stringnull)。

一旦你写了任意一个带参构造,编译器就不再送默认版本。下面这个例子中,若你只留带参构造,再写 new Person() 就会报错。

namespace CSharpDemo;

class Person
{
    public string Name;
    public int Age;

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

var p = new Person("小明", 18);
// var q = new Person();  // 编译错误:没有无参构造
Tip

新手常踩的坑:忘了写无参构造却还在用 new Person()。记住——写了带参构造,默认的无参构造就「消失」了。

带参构造:创建即赋值

带参构造让你在 new 的同时就把核心数据传进去,避免对象处于「半成品」状态。字段先声明、构造里再赋值,这是最清楚的顺序。

namespace CSharpDemo;

class Student
{
    public int Id;
    public string Name;
    public int Score;

    public Student(int id, string name, int score)
    {
        Id = id;
        Name = name;
        Score = score;
    }
}

var s = new Student(1, "小红", 95);
Console.WriteLine($"{s.Name} 考了 {s.Score} 分");

字段初始化器与执行顺序

除了构造里赋值,你还能在声明字段时直接给初值,这叫字段初始化器(field initializer)。执行顺序是:先跑字段初始化器,再跑构造函数体。

namespace CSharpDemo;

class Counter
{
    private int total = 10;          // 先执行
    public Counter()
    {
        total++;                     // 后执行,total 变成 11
    }
    public int Get() => total;
}

Console.WriteLine(new Counter().Get());  // 11
Note

当多个构造彼此重叠时,别把同一段赋值逻辑复制好几遍。用 this 链式调用把公共部分收口,见下节。

this 链式调用:一个构造调用另一个

this(...) 可以在一个构造函数里调用同类的另一个构造函数。常见做法是:复杂的带参构造放主逻辑,简单的构造用 this 把缺省值补齐后转交给它。

namespace CSharpDemo;

class Product
{
    public int Id;
    public string Name;
    public decimal Price;

    public Product(int id, string name, decimal price)
    {
        Id = id;
        Name = name;
        Price = price;
    }

    // 没给价格时,默认 9.99m,转交给上面的构造
    public Product(int id, string name) : this(id, name, 9.99m)
    {
    }
}

var p1 = new Product(1, "笔记本");
var p2 = new Product(2, "键盘", 199.00m);
Console.WriteLine($"{p1.Name} 默认价 {p1.Price}");   // 9.99

this(id, name, 9.99m) 会先执行那个三参构造,再回来执行本构造体(这里为空)。这避免了重复赋值。

构造也可以和对象初始化器配合

如果你留着可写的属性(如 { get; set; }),既能用带参构造,也能在 new 后顺手补字段。两者不冲突。

namespace CSharpDemo;

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public Person(string name) => Name = name;
}

var p = new Person("小刚") { Age = 20 };
Console.WriteLine($"{p.Name}, {p.Age}");
Tip

构造负责「必须给的」数据,对象初始化器负责「可选补的」数据,分工很清晰。

现代写法:主构造器(C# 12)

C# 12 起可以给类直接加「主构造器(primary constructor)」参数,参数在整个类体内可用,省掉手写字段和赋值。但本书第 91 章会专门展开,这里先见个面。

namespace CSharpDemo;

class Point(int x, int y)
{
    public int X { get; } = x;
    public int Y { get; } = y;
}

var pt = new Point(3, 4);
Console.WriteLine($"({pt.X}, {pt.Y})");
Note

主构造器很简洁,但它和 this(...) 链式调用是两套思路,不能同时混用。初学先吃透传统构造,再学这个。

为什么不用公开字段直接赋值

你也许会想:干脆把字段 public 公开,创建后一个个赋值不也行?问题在于顺序和完整性都没保障——字段可能只赋了一半,对象就已被别处拿去使用,还可能被塞进非法值。构造函数把「必要的初始化」收口在一个入口,对象一出生就是合法状态。

构造里顺手做参数校验

构造函数不只是赋值,也可以把关。下面要求年龄不能为负,否则直接抛异常,把错误挡在对象诞生那一刻。

namespace CSharpDemo;

class Person
{
    public string Name { get; }
    public int Age { get; }
    public Person(string name, int age)
    {
        if (age < 0) throw new ArgumentException("年龄不能为负");
        Name = name;
        Age = age;
    }
}

var p = new Person("小明", 18);   // 正常
// var bad = new Person("X", -1); // 抛异常
Tip

新手踩坑:把校验全丢给属性、new 之后才检查,往往已经晚了。创建时就该保证合法,这正是构造存在的价值。

字段初始化器与构造赋值怎么配合

字段初始化器先于构造体执行,且对每个构造都生效。当你希望「所有对象都先有个基础默认值,再被构造加工」,用它最合适。只想给特定构造特殊初值时,写在构造体内更精准。

namespace CSharpDemo;

class Logger
{
    private List<string> logs = new();     // 每个对象都有独立列表
    public Logger() => logs.Add("启动");
    public void Add(string s) => logs.Add(s);
    public int Count => logs.Count;
}

Console.WriteLine(new Logger().Count);    // 1,初始化器先跑

构造与对象初始化器怎么选

两者不冲突,但职责不同:构造函数承担「缺它不可」的核心数据,对象初始化器承担「可选补充」。如果一个类有一半字段都是可选的,硬写一大串构造参数反而难用,这时保留简单构造、用初始化器补选项更清爽。

Note

经验:必填且影响对象身份的数据放进构造;可有可无的,留给 { ... } 初始化器。

常见误区:构造里别做太重的事

构造应当快进快出。若在构造里读文件、连数据库、开线程,对象创建会又慢又易错,且出错时对象处于半生不熟状态。重活应拆成独立方法,等对象建好后再调用。

何时需要多个构造函数

当对象的创建方式不止一种时,提供多个构造最贴心:一种只给核心数据,一种允许带可选参数,再用 this(...) 归并。但若参数组合爆炸(五六个构造互相叠加),就该考虑「主构造器 + 对象初始化器」或工厂方法,避免构造泛滥。

Note

权衡点:构造太多会让调用方选择困难。一般保留「最全的一个」加「一两个常用简写」即可,其余交给初始化器补。

小结

构造函数在 new 时自动执行,名字与类相同、无返回类型。没写构造会有默认无参版本;写了带参构造它就不自动给了。this(...) 能把公共初始化收口,字段初始化器先于构造体执行。把「必须的数据」交给构造,「可选的数据」交给初始化器,对象从一出生就干净完整。构造里顺手校验能把非法值挡在源头,但别把重活塞进构造;构造函数数量也应克制,避免调用方选择困难。