1. 入门
假设我们有个Person类:
public class Person
{
public string Name;
public int Age;
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
你是不是觉得自己快成天才了——可以随便创建带名字和年龄的人。但问题来了:有时候信息不够!比如用户只输入了名字,年龄要晚点才知道。或者你想有时候给个“默认”年龄。你当然可以想点别的办法,但C#有个优雅的解决方案——构造函数重载。
构造函数重载就是一个类可以有多个同名构造函数(和类名一样),但参数列表不同。
这些参数列表有啥区别?可以有这些不同:
- 参数数量:比如一个构造函数2个参数,另一个3个。
- 参数类型:一个是(string, int),另一个是(string, decimal)。
- 参数顺序:(string, int)和(int, string)——这俩签名不一样。
最重要的规则:编译器是靠签名区分构造函数(和任何方法)的。签名包括构造函数(或方法)名和参数列表(数量、类型、顺序)。访问修饰符(public、private)和普通方法的返回类型不算签名一部分。构造函数没有返回类型,名字总和类名一样。
2. 构造函数重载:语法和例子
public class Cat
{
public string Name;
public string Color;
// 无参数构造函数
public Cat()
{
Name = "无名猫";
Color = "灰色";
}
// 一个参数的构造函数
public Cat(string name)
{
Name = name;
Color = "灰色";
}
// 两个参数的构造函数
public Cat(string name, string color)
{
Name = name;
Color = color;
}
}
用法:
Cat cat1 = new Cat(); // "无名猫", "灰色"
Cat cat2 = new Cat("穆尔兹克"); // "穆尔兹克", "灰色"
Cat cat3 = new Cat("瑞吉克", "橙色"); // "瑞吉克", "橙色"
创建对象时你传啥参数,C#会根据参数数量和类型自动选对的构造函数。
这真的很方便!用你这个类的人不用填他不知道的参数:只想要只猫——用无参数构造函数,知道名字——用另一个,想指定颜色——用第三个。
3. 构造函数重载怎么选?
C#会在编译时自动根据你传的参数类型和数量选对的构造函数。很难出错——要是搞错了,编译器会直接报错。
我们加个带int参数的构造函数试试:
public Cat(int age)
{
Name = "无名猫";
Color = "灰色";
// 年龄相关的代码
}
现在我们还能按年龄创建“猫咪”:
Cat oldCat = new Cat(5); // 会调用Cat(int age)构造函数
| 构造函数调用 | 会调用哪个? |
|---|---|
|
无参数 |
|
一个string参数 |
|
两个string参数 |
|
一个int参数 |
4. 构造函数内部调用:this关键字
有时候不同构造函数做的事差不多。为了不复制代码,可以“一个构造函数里调用另一个”。这就用this关键字。
public class Cat
{
public string Name;
public string Color;
public Cat() : this("无名猫")
{
// 这个构造函数会调用Cat(string name)
}
public Cat(string name) : this(name, "灰色")
{
// 这个构造函数会调用Cat(string name, string color)
}
public Cat(string name, string color)
{
Name = name;
Color = color;
}
}
结果就是:
- new Cat()会调用Cat(string name),它又会调用Cat(string name, string color)。
- 所有“路都通罗马”:所有初始化都集中在一个“主”构造函数里。
这种写法叫constructor chaining(构造函数链式调用)。
5. 生活中的例子
来写个游戏里的英雄类(Hero),有名字和等级!代码如下:
只有一个构造函数:
public class Hero
{
public string Name;
public int Level;
public Hero(string name, int level)
{
Name = name;
Level = level;
}
}
现在我们让它能多种方式创建!
多个构造函数:
public class Hero
{
public string Name;
public int Level;
// 如果啥都没填,英雄就是“无名”1级
public Hero() : this("无名", 1)
{
}
// 只知道名字,等级默认1
public Hero(string name) : this(name, 1)
{
}
// 最全的构造函数
public Hero(string name, int level)
{
Name = name;
Level = level;
}
}
现在可以这样创建英雄:
Hero h1 = new Hero(); // "无名", 1
Hero h2 = new Hero("阿图尔"); // "阿图尔", 1
Hero h3 = new Hero("洛拉", 10); // "洛拉", 10
6. 实现细节:隐藏的坑和注意点
第一:如果你自己写了任何一个构造函数,编译器就不会自动帮你生成“空”构造函数。所以如果你写了带参数的构造函数,但忘了写无参数的,这样写:
Hero h = new Hero(); // 没有无参数构造函数会报错!
会编译报错。
第二:构造函数链里要是调用错了,初始化逻辑就乱了。最好所有实际初始化都放在最全的构造函数里,其他的只用this(...)把数据传过去。
第三:如果参数只差类型(比如Cat(string s)和Cat(object o)),用null做参数时可能会让编译器懵圈,不知道你想调哪个构造函数。
7. 重载和字段初始化
类里经常有些字段必须要赋值。构造函数重载让你方便地做到这一点,但默认值要你自己决定啥合适。
带校验的例子:
public class Book
{
public string Title;
public int Year;
// 默认书名“无标题”,年份2000
public Book() : this("无标题", 2000)
{
}
public Book(string title) : this(title, 2000)
{
}
public Book(string title, int year)
{
Title = title;
Year = year;
}
}
8. 不同参数组合的重载
有的类构造函数能有几十个!这不一定是好架构(有时候用“配置”方法或builder模式更好),但对用户对象、模型、DTO来说——很常见。
例子:一个“宠物”类,可以全填,也可以只填最重要的。
public class Pet
{
public string Name;
public int Age;
public string Type;
public bool IsVaccinated;
// 默认构造函数
public Pet() : this("NoName", 0, "Cat", false) { }
// 最少信息
public Pet(string name, string type) : this(name, 0, type, false) { }
// 最全的构造函数
public Pet(string name, int age, string type, bool isVaccinated)
{
Name = name;
Age = age;
Type = type;
IsVaccinated = isVaccinated;
}
}
方法重载和构造函数重载的区别
| 普通方法 | 构造函数(包括重载) |
|---|---|
| 随时都能调用 | 只有创建对象时(new)才会调用 |
| 可以返回任意类型的值 | 永远不返回值(没有类型) |
| 方法名随便起,通常是动词 | 名字总和类名一样 |
| 可以按参数重载 | 也可以按参数重载 |
GO TO FULL VERSION