在学习C#的过程中,"类(Class)"是一个绕不开的核心知识点。无论是控制台程序、桌面程序、还是Web应用开发,类几乎贯穿了整个C#的编程过程。可以说,掌握类的定义与使用,是进入面向对象编程的重要一步。
接下来我会从类的基本概念、组成部分、访问修饰符、构造函数、静态成员、继承、多态等方面进行介绍,会有相关具体的案例程序,建议跟着敲一遍学习理解。
一、什么是类
在C#中,类是对象的模板 ,它定义了某类事物具有哪些属性 和行为。例如:我们可以把一个学生看成一个类:
- 学生有姓名、年龄等属性
- 学生有自我介绍、学习等行为
当我们根据这个类创建具体对象时,就得到了一个真实的学生实例。
1.1类与对象的关系
可以把类和对象理解为:
类:图纸、模板
对象:按照图纸造出来的具体实体
比如:
Student是一个类
student1、student2是由Student创建出来的对象
二、定义一个最简单的类
下面是一个最基础的Student类示例:
cs
using System;
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public void Introduce()
{
Console.WriteLine($"Hello, my name is {Name} and I am {Age} years old.");
}
}
public class Program
{
public static void Main(string[] args)
{
Student student = new Student { Name = "Alice", Age = 20 };
student.Introduce();
}
}
运行结果

在这段代码中:Student是类,Name和Age是属性。Introduce()是方法,student是创建出来的对象。这段代码展示了类最基本的使用方式。
三、类的组成部分
一个完成的类通常是由以下几个部分构成:字段 、属性 、方法、构造函数。在下面会分别进行说明。
3.1字段
字段用于在类中保存数据
cs
public class Student
{
public string name;
public int age;
}
虽然字段可以直接存储数据,但在实际开发中,不建议把字段直接暴露为public。因为这样会导致外部可以随意修改数据,不利于封装和数据安全。因此,C#中更推荐使用属性来管理数据。
注意:C#类里的成员默认是私有的,这点跟C++不一样。
3.2属性
属性可以理解为对字段的一层封装。它既能像变量一样方便访问,又能在读取和赋值时加入逻辑控制 。从表面上看,属性的使用方式和字段很像,都是通过"对象.成员名"的方式进行访问,但是从本质上来说,属性并不是单纯的变量,而是一种提供受控访问的成员。
简单来说:
字段更偏向于直接存储数据
属性更偏向于通过方法的方式访问数据
因此,在实际开发中,通常推荐使用属性而不是直接公开字段。
3.2.1属性的基本语法
属性通常由两个访问器组成:
- get:读取属性值
- set:设置属性值
基本示例
cs
public class Student
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
这里name是私有字段,Name是公开属性,外部可以通过Name访问数据,而不是直接访问name
get访问器
get用于返回属性值,也就是"读取数据"。
cs
get {return name;}
当我们执行:
cs
Console.WriteLine(student.Name);
实际上就是调用get。
set访问器
set用于给属性赋值,也就是写入数据
cs
set { name = value; }
当我们执行:
cs
student.Name = "张三";
实际上就是调用set。这里的value是C#自动提供的一个隐含参数,表示外部传入的新值。
例如:
cs
student.Name = "李四";
那么在set中,value就等于李四。
3.2.2自动实现属性
如果属性不需要额外的逻辑,比如不需要校验、不需要格式处理,那么可以使用更简洁的写法:自动实现属性
cs
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
}
这种写法由编译器自动生成隐藏字段,因此代码更加简洁。
示例:
cs
Student student = new Student();
student.Name = "王五";
student.Age = 20;
Console.WriteLine($"姓名:{student.Name},年龄:{student.Age}");
自动实现属性适用于简单的数据保存场景,也是实际开发中最常见的写法之一。
3.2.3带私有字段的属性
如果属性需要添加校验逻辑、格式控制或其他操作,就需要手动顶一个私有字段作为后备存储。
cs
using System;
public class Student
{
private int age;
public string Name { get; set; }
public int Age
{
get { return age; }
set
{
if (value < 0 || value > 150)
{
Console.WriteLine("年龄输入不合法!");
}
else
{
age = value;
}
}
}
}
测试代码
cs
public class Program
{
public static void Main()
{
Student student = new Student();
student.Age = 20;
Console.WriteLine(student.Age);
student.Age = -5;
Console.WriteLine(student.Age);
}
}
输出结果

这种写法体现了属性的真正价值:不只是保存数据,还能控制数据。
3.2.4只读属性
有些数据只允许读取,不允许在类外随意修改,这时可以只保留get。
示例
cs
public class Person
{
public string IdCard { get; }
public Person(string idCard)
{
IdCard = idCard;
}
}
使用示例
cs
Person person = new Person("123456789");
Console.WriteLine(person.IdCard);
这种属性在对象创建后就不能再随意修改,适合表示一些固定不变的数据,比如:
身份证号、学号、创建时间、唯一编号等
3.2.5访问器使用不同的访问级别
属性本身可以是public,但get和set也可以拥有不同的访问权限。
例如我们希望某个属性可以被外部读取,但只能在类内部修改:
cs
public class Account
{
public decimal Balance { get; private set; }
public void Deposit(decimal amount)
{
if (amount > 0)
{
Balance += amount;
}
}
}
使用示例
cs
Account account = new Account();
account.Deposit(500);
Console.WriteLine(account.Balance);
// account.Balance = 1000; // 这里会报错,因为 set 是 private
这种设计非常常见,优点是外部可以查看数据但不能直接篡改数据,必须要通过类提供的方法来修改,这样能更好的保护对象内部状态。
3.2.6init访问器:只允许初始化时赋值
在较新的C#版本中,还可以使用init访问器。它表示属性只能在对象初始化时赋值,之后不能再修改。
示例
cs
public class Student
{
public string Name { get; init; }
public int Age { get; init; }
}
使用方式
cs
Student student = new Student
{
Name = "小明",
Age = 20
};
Console.WriteLine($"{student.Name} - {student.Age}");
创建完后再修改
cs
// student.Name = "小红"; // 报错
init的特点是比set更严格,适合初始化后不可改变的对象,常用于配置对象、数据模型对象。
3.2.7表达式属性
对于逻辑简单的属性,可以使用更简洁的写法,成为表达式体属性。
示例
cs
public class Circle
{
public double Radius { get; set; }
public double Area => 3.14 * Radius * Radius;
}
这里的Area是一个只读计算属性,等价于:
cs
public double Area
{
get { return 3.14 * Radius * Radius; }
}
表达式体属性适合用于代码简单,返回值明确,不需要复杂逻辑的场景,这样写代码会更简洁,也更现代化。
3.2.8属性与字段的区别
可以参考下面这张表格:
|---------|------------|-----------|
| 对比项 | 字段 | 属性 |
| 本质 | 存储数据的变量 | 封装数据访问的成员 |
| 是否可控制访问 | 较弱 | 较强 |
| 是否能加逻辑 | 不方便 | 可以 |
| 使用建议 | 通常为private | 对外通常使用属性 |
建议写法:一般都是字段用于内部存储,属性对外暴露
cs
public class Student
{
private int age; // 字段
public int Age // 属性---属性首字母要大写
{
get { return age; }
set
{
if (value >= 0)
{
age = value;
}
}
}
}
3.2.9属性的小案例
下面用一个商品类Product来综合展示属性的几种常见写法(建议跟着敲一遍,好好体会属性的各种写法)
cs
using System;
public class Product
{
private decimal price;
// 自动实现属性
public string Name { get; set; }
// 只读属性
public string ProductCode { get; }
// 带校验逻辑的属性
public decimal Price
{
get { return price; }
set
{
if (value < 0)
{
Console.WriteLine("商品价格不能小于 0!");
}
else
{
price = value;
}
}
}
// 计算属性
public string DisplayInfo => $"商品编号:{ProductCode},名称:{Name},价格:{Price} 元";
public Product(string productCode, string name, decimal price)
{
ProductCode = productCode;
Name = name;
Price = price;
}
}
public class Program
{
public static void Main()
{
Product product = new Product("P1001", "机械键盘", 299);
Console.WriteLine(product.DisplayInfo);
product.Price = -50; // 非法赋值
Console.WriteLine(product.DisplayInfo);
}
}
输出结果

3.3方法
方法是用于描述类或对象的行为,也就是能做什么。如果说属性表示对象有什么,那么方法就是表示对象能做什么。例如,学生可以学习,银行账户可以存款和取款。
3.3.1方法的基本概念
方法本质上是一段可以重复调用的代码,用来完成某个具体功能。再类中定义方法,可以让对象拥有行为,并提高代码的复用性和可读性。
基本语法
cs
访问修饰符 返回值类型 方法名(参数列表)
{
// 方法体
}
例如
cs
public void SayHello()
{
Console.WriteLine("你好,欢迎学习 C#!");
}
这个方法中,public表示访问修饰符,void表示没有返回值,SayHello是方法名,()表示没有参数,{}中是方法执行的代码
3.3.2无参无返回值方法
这是最简单的方法形式,适合执行某个动作,但不需要接收输入,也不需要返回结果。
示例
cs
using System;
public class Student
{
public void Introduce()
{
Console.WriteLine("大家好,我是一名学生。");
}
}
public class Program
{
public static void Main()
{
Student student = new Student();
student.Introduce();
}
}
输出结果

这种方法适合用于简单输出、提示信息、执行固定操作等场景。
3.3.3有参无返回值方法
如果方法执行时需要外部提供数据,就可以使用参数。
示例:学习某门课程
cs
using System;
public class Student
{
public string Name { get; set; }
public void Study(string subject)
{
Console.WriteLine($"{Name}正在学习 {subject}。");
}
}
public class Program
{
public static void Main()
{
Student student = new Student();
student.Name = "张三";
student.Study("C#");
}
}
输出结果

这里的subject就是方法参数,表示调用方法时传入的数据。
3.3.4无参有返回值方法
有些方法不需要接收参数,但需要返回一个结果
示例:获取问候语
cs
using System;
public class Greeting
{
public string GetMessage()
{
return "欢迎来到 C# 的世界!";
}
}
public class Program
{
public static void Main()
{
Greeting greeting = new Greeting();
string message = greeting.GetMessage();
Console.WriteLine(message);
}
}
输出结果:这里return返回了一个字符串结果

3.3.5有参有返回值方法
这是最常见的一种方法形式,既能接收输入,又能返回处理结果。
示例:计算两个数的和
cs
using System;
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
}
public class Program
{
public static void Main()
{
Calculator calculator = new Calculator();
int result = calculator.Add(3, 5);
Console.WriteLine($"计算结果:{result}");
}
}
输出结果:这种方法特别适合做数据处理、计算、查询等操作

3.3.6return 关键字
在有返回值的方法中,通常需要使用return把结果返回给调用者
例如:
cs
public int Square(int number)
{
return number * number;
}
如果方法返回类型不是void,那么通常必须返回一个符合类型要求的值。而如果方法返回类型是void,则表示不返回结果:
cs
public void PrintMessage()
{
Console.WriteLine("这是一条消息。");
}
3.3.7方法参数
参数是方法与外部交互的重要方式。通过参数,方法可以接收调用者传入的数据。
示例:
cs
public void ShowInfo(string name, int age)
{
Console.WriteLine($"姓名:{name},年龄:{age}");
}
调用方法时:
cs
ShowInfo("李四", 20);
这里:string name和int age都时属于参数。
3.3.8方法重载
在同一个类中,允许定义多个同名方法,只要它们的参数列表不同即可,这叫做方法重载。
方法重载的判断依据主要是:参数的个数不同 ,参数的类型不同 ,参数的顺序不同,
返回值类型不同,不能单独构成重载。
示例:加法方法重载
cs
using System;
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
public double Add(double a, double b)
{
return a + b;
}
public int Add(int a, int b, int c)
{
return a + b + c;
}
}
public class Program
{
public static void Main()
{
Calculator calculator = new Calculator();
Console.WriteLine(calculator.Add(2, 3));
Console.WriteLine(calculator.Add(2.5, 3.1));
Console.WriteLine(calculator.Add(1, 2, 3));
}
}
输出结果

方法重载的好处是:同一个功能可以根据不同的输入方式灵活调用,提高代码的可读性。
3.3.9可选参数
在C#中,方法参数可以设置默认值,这样调用时可以不传这个参数。
示例:
cs
using System;
public class MessageService
{
public void SendMessage(string message, string receiver = "管理员")
{
Console.WriteLine($"向 {receiver} 发送消息:{message}");
}
}
public class Program
{
public static void Main()
{
MessageService service = new MessageService();
service.SendMessage("系统运行正常");
service.SendMessage("会议将在下午开始", "张老师");
}
}
输出结果

可选参数的优点是简化调用方式,适合某些参数在大多数情况下都有固定默认值的场景。
3.3.10命名参数
调用方法时,可以通过"参数名:值"的方式明确指定参数,这叫做命名参数
示例
cs
using System;
public class Student
{
public void Register(string name, int age, string major)
{
Console.WriteLine($"姓名:{name},年龄:{age},专业:{major}");
}
}
public class Program
{
public static void Main()
{
Student student = new Student();
student.Register(age: 20, major: "计算机科学", name: "王五");
}
}

命名参数可以提高代码可读性,特别是在参数较多时很有帮助。
3.3.11ref、out和in参数
这是方法参数中进阶的内容,涉及到了值传递和引用传递的概念,不理解可以先往后看,了解了值传递和引用再看就能理解了。
ref参数
ref表示按引用传递参数,调用方法前,变量必须先初始化。方法内部对参数的修改会影响到外部变量。
示例
cs
using System;
public class Calculator
{
public void AddTen(ref int number)
{
number += 10;
}
}
public class Program
{
public static void Main()
{
Calculator calculator = new Calculator();
int value = 5;
calculator.AddTen(ref value);
Console.WriteLine(value);
}
}
输出结果

out参数
out也表示按引用传递,但调用前不要求变量已赋值。方法内部必须对out参数赋值。
示例
cs
using System;
public class Division
{
public bool TryDivide(int a, int b, out int result)
{
if (b == 0)
{
result = 0;
return false;
}
result = a / b;
return true;
}
}
public class Program
{
public static void Main()
{
Division division = new Division();
if (division.TryDivide(10, 2, out int result))
{
Console.WriteLine($"结果是:{result}");
}
else
{
Console.WriteLine("除数不能为 0");
}
}
}
输出结果

out常用于方法返回多个结果的场景。
in参数
in表示按引用传递,但方法内部不能修改参数,它适合传递较大的数据结构,同时又不希望被修改。
示例
cs
public void PrintValue(in int number)
{
Console.WriteLine(number);
}
3.3.12实例方法与静态方法
方法可以分为实例方法 和静态方法
实例方法属于对象,必须通过对象来调用。
cs
public class Student
{
public string Name { get; set; }
public void SayHello()
{
Console.WriteLine($"你好,我是{Name}");
}
}
调用方式
cs
Student student = new Student { Name = "小明" };
student.SayHello();
实例方法通常用于操作对象自己的数据。
静态方法
静态方法属于类本身,不依赖具体对象。
cs
using System;
public class MathTool
{
public static int Multiply(int a, int b)
{
return a * b;
}
}
public class Program
{
public static void Main()
{
int result = MathTool.Multiply(3, 4);
Console.WriteLine(result);
}
}
输出结果

静态方法适合处理哪些不依赖于对象状态的公共功能。
3.3.13表达式体方法
对于逻辑非常简单的方法,可以使用表达式体语法,让代码更加简洁。
示例
cs
public class Calculator
{
public int Square(int x) => x * x;
}
等价于
cs
public class Calculator
{
public int Square(int x)
{
return x * x;
}
}
3.3.14方法中的访问修饰符
和属性一样,方法也可以通过访问修饰符控制可见范围。常见的修饰符是:
public:任何地方都可以访问
private:只能在当前类内部访问
protected:只能在当前类和子类中访问
internal:只能在当前程序集内访问
示例
cs
using System;
public class BankAccount
{
public string AccountName { get; set; }
public void ShowInfo()
{
Console.WriteLine($"账户名:{AccountName}");
LogOperation();
}
private void LogOperation()
{
Console.WriteLine("记录日志:调用了 ShowInfo 方法。");
}
}
在这里,ShowInfo()是公开方法,外部可以调用。LogOperation()是私有方法,只能在内部调用。这样设计可以隐藏实现细节,让类的结果更加清晰。具体等下也会在类的继承、多态再做讨论。
3.3.15局部函数
C#还支持在方法内部定义方法,这种方法叫做局部函数。
示例
cs
using System;
public class Calculator
{
public int Calculate()
{
int Add(int a, int b)
{
return a + b;
}
return Add(3, 5);
}
}
局部函数适合哪些只在方法内部使用的小逻辑。
3.3.16一个完整的方法案例:银行账户类
同样为了让你更清晰的体会C#类方法的使用,提供了一个银行账户类:
包含账户名、余额、存款方法、取款方法、显示账户信息的方法
cs
using System;
public class BankAccount
{
public string AccountName { get; set; }
public decimal Balance { get; private set; }
public BankAccount(string accountName, decimal balance)
{
AccountName = accountName;
Balance = balance;
}
public void Deposit(decimal amount)
{
if (amount <= 0)
{
Console.WriteLine("存款金额必须大于 0。");
return;
}
Balance += amount;
Console.WriteLine($"存款成功,金额:{amount},当前余额:{Balance}");
}
public void Withdraw(decimal amount)
{
if (amount <= 0)
{
Console.WriteLine("取款金额必须大于 0。");
return;
}
if (amount > Balance)
{
Console.WriteLine("余额不足,取款失败。");
return;
}
Balance -= amount;
Console.WriteLine($"取款成功,金额:{amount},当前余额:{Balance}");
}
public void ShowInfo()
{
Console.WriteLine($"账户名:{AccountName},余额:{Balance}");
}
}
public class Program
{
public static void Main()
{
BankAccount account = new BankAccount("张三", 1000);
account.ShowInfo();
account.Deposit(500);
account.Withdraw(300);
account.Withdraw(2000);
account.ShowInfo();
}
}
输出结果

3.4构造函数
构造函数是类中的一种特殊方法,用于在创建对象时完成初始化工作。当我们使用new关键字创建对象时,构造函数会被自动调用。构造函数的主要作用是:让对象在一开始就处在一个合理、可用的状态。
例如,一个学生对象在创建时,通常就应该有姓名和年龄。一个银行账户对象在创建时,通常就应该有账户名和初始余额。这些初始化工作就非常适合放在构造函数中完成。
3.4.1构造函数的基本特点
- 构造函数与普通方法不同,它有以下几个明显特征。
- 名称必须与类名相同
- 没有返回值类型,连void也不能写
- 创建对象时自动调用
- 主要用于初始化对象
示例
cs
using System;
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public Student()
{
Name = "未命名";
Age = 18;
}
public void ShowInfo()
{
Console.WriteLine($"姓名:{Name},年龄:{Age}");
}
}
public class Program
{
public static void Main()
{
Student student = new Student();
student.ShowInfo();
}
}
输出结果

这里的Student()就是构造函数。当执行new Student()时,它会自动给对象的属性赋初始值。
3.4.2无参构造函数
无参构造函数指的是没有参数的构造函数,常用于给对象设置默认值。
示例
cs
public class Car
{
public string Brand { get; set; }
public string Color { get; set; }
public Car()
{
Brand = "未知品牌";
Color = "白色";
}
}
使用实例
cs
Car car = new Car();
Console.WriteLine($"{car.Brand} - {car.Color}");
输出结果

无参构造就适合给对象设置默认状态,简化对象创建过程,方便某些框架进行实例化。
3.4.3有参构造函数
有参构造函数可以在创建对象时直接传入必要的数据,让对象一开始就具备完整信息。
示例
cs
using System;
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public Student(string name, int age)
{
Name = name;
Age = age;
}
public void ShowInfo()
{
Console.WriteLine($"姓名:{Name},年龄:{Age}");
}
}
public class Program
{
public static void Main()
{
Student student = new Student("张三", 20);
student.ShowInfo();
}
}
输出结果

这种方式比先创建对象,再逐个赋值更直接,也更容易保证数据完整性。
3.4.4构造函数重载
一个类可以定义多个构造函数,只要它们的参数列表不同即可,这叫做构造函数重载。
示例
cs
using System;
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public Student()
{
Name = "未命名";
Age = 18;
}
public Student(string name)
{
Name = name;
Age = 18;
}
public Student(string name, int age)
{
Name = name;
Age = age;
}
}
public class Program
{
public static void Main()
{
Student s1 = new Student();
Student s2 = new Student("李四");
Student s3 = new Student("王五", 21);
Console.WriteLine($"{s1.Name} - {s1.Age}");
Console.WriteLine($"{s2.Name} - {s2.Age}");
Console.WriteLine($"{s3.Name} - {s3.Age}");
}
}
输出结果

构造函数重载的好处是提供了多种创建对象的方式,提高了灵活性,可以满足不同场景下的初始化需求。
3.4.5使用this调用其他构造函数
当一个类中有多个构造函数时,可能会出现重复代码。这是可以通过this(...)在一个构造函数中调用另一个构造函数,从而减少重复。
示例
cs
using System;
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public Student() : this("未命名", 18)
{
}
public Student(string name) : this(name, 18)
{
}
public Student(string name, int age)
{
Name = name;
Age = age;
}
}
public class Program
{
public static void Main()
{
Student s1 = new Student();
Student s2 = new Student("小明");
Student s3 = new Student("小红", 20);
Console.WriteLine($"{s1.Name} - {s1.Age}");
Console.WriteLine($"{s2.Name} - {s2.Age}");
Console.WriteLine($"{s3.Name} - {s3.Age}");
}
}
输出结果

3.4.6默认构造函数
如果一个类中没有写任何构造函数,那么C#编译器会自动提供一个默认的无参构造函数。
例如:
cs
public class Person
{
public string Name { get; set; }
}
这个类虽然没有显式写构造函数,但仍然可以这样创建对象
cs
Person person = new Person();
不过需要注意的是:一旦你自己定义了任意构造函数,编译器就不会再自动生成默认无参构造函数。
例如:
cs
public class Person
{
public string Name { get; set; }
public Person(string name)
{
Name = name;
}
}
这是如果你写:
cs
Person p = new Person(); // 会报错
就会出错,因为无参构造函数已经不存在了。如果你既需要无参构造,也需要有参构造,就必须自己都写出来。
3.4.7构造函数与对象初始化器
除了使用构造函数,C#还支持对象初始化器来给属性赋值。
示例
cs
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
}
创建对象时可以这样写
cs
Student student = new Student
{
Name = "赵六",
Age = 22
};
这和下面这种方式相比更简洁
cs
Student student = new Student();
student.Name = "赵六";
student.Age = 22;
构造函数和对象初始化器的区别
构造函数更适合初始化对象创建时必须具备的数据,对象初始化器更适合可选属性的赋值。如果某些数据是对象必须有的,通常建议放到构造函数里,如果只是附加设置,可以放到对象初始化器中。
3.4.8构造函数中也可以加入校验逻辑
构造函数不仅能赋值,还可以检查传入参数是否合法
示例
cs
using System;
public class Book
{
public string Title { get; set; }
public double Price { get; set; }
public Book(string title, double price)
{
if (string.IsNullOrWhiteSpace(title))
{
throw new ArgumentException("书名不能为空");
}
if (price < 0)
{
throw new ArgumentException("价格不能为负数");
}
Title = title;
Price = price;
}
}
使用示例
cs
Book book = new Book("C# 入门教程", 58.8);
Console.WriteLine($"{book.Title} - {book.Price}");
这种写法的意义在于,从对象创建的第一刻开始,就保证它是合法的。这也是构造函数非常重要的价值之一。
3.4.9静态构造函数
除了普通构造函数,C#还有一种特殊的构造函数,叫做静态构造函数,用于初始化类本身的静态成员。它的特点是使用static修饰,没有访问修饰符,没有参数,一个类中最多只能有一个静态构造函数,再类第一次使用前自动执行一次,不能手动调用。
示例
cs
using System;
public class School
{
public static string SchoolName { get; set; }
static School()
{
SchoolName = "实验中学";
Console.WriteLine("静态构造函数执行了。");
}
}
public class Program
{
public static void Main()
{
Console.WriteLine(School.SchoolName);
Console.WriteLine(School.SchoolName);
}
}
输出结果

从结果来看:静态构造函数只执行了一次,及时后面多次访问类,也不会重复执行。静态构造函数适合初始化静态字段,执行类级别的一次性准备工作。我们初学阶段了解即可,不需要过度深入。
3.4.10私有构造函数
构造函数还可以声明为private,表示外部不能直接创建该类的对象。
示例
cs
public class Utility
{
private Utility()
{
}
public static void PrintMessage()
{
Console.WriteLine("这是一个工具类。");
}
}
此时外部不能这样写
cs
public class Utility
{
private Utility()
{
}
public static void PrintMessage()
{
Console.WriteLine("这是一个工具类。");
}
}
只能通过静态成员使用该类
私有构造函数的常见用途是防止类被实例化,配合单例模式使用,工具类只提供静态方法时使用。
3.4.11构造函数与普通方法的区别
具体可以参考下表
|------|-----------|-----------|
| 对比项 | 构造函数 | 普通方法 |
| 名称 | 必须与类名相同 | 可自定义 |
| 返回值 | 没有返回值类型 | 可以有,也可以没有 |
| 调用时机 | 创建对象时自动调用 | 需要手动调用 |
| 作用 | 初始化对象 | 完成功能操作 |
3.5this关键字
this表示当前对象本身,当成员变量和方法参数同名时,通常使用this来区分
示例
cs
using System;
public class Student
{
private string name;
public Student(string name)
{
this.name = name;
}
public void ShowName()
{
Console.WriteLine(this.name);
}
}
public class Program
{
public static void Main()
{
Student student = new Student("小红");
student.ShowName();
}
}
输出结果

3.6继承
在面向对象编程中,继承、多态、抽象类是彼此紧密联系的知识点。它们共同帮助我们建立更清晰的类结构,减少重复代码,并让程序具有更好的拓展性。可以用一句话分别概括他们之间的关系。
- 继承:让子类复用父类的成员
- 抽象类:让父类只描述应该有什么,而不一定提供完整实现
- 多态:让相同的调用方式,在不同对象上产生不同效果
3.6.1继承的基本概念
继承是面向对象的三大特性之一。它允许我们基于已有类创建新类,新类会自动获得原有类中的部分成员。
简单来说:
如果多个类具有共同特征,就可以把这些共同内容提取到一个父类中,再让其他类继承它。
例如:学生和老师都属于人,猫和狗都属于动物。这些关系都符合**"是一个"(is-a)**的特点,因此适合使用继承。
3.6.2为什么要使用继承
如果没有继承,不同类中很容易出现重复代码。
不适用继承的写法
cs
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public void ShowInfo()
{
Console.WriteLine($"姓名:{Name},年龄:{Age}");
}
}
public class Teacher
{
public string Name { get; set; }
public int Age { get; set; }
public void ShowInfo()
{
Console.WriteLine($"姓名:{Name},年龄:{Age}");
}
}
这里的Student和Teacher的很多内容是重复的。
使用继承后的写法
cs
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public void ShowInfo()
{
Console.WriteLine($"姓名:{Name},年龄:{Age}");
}
}
public class Student : Person
{
}
public class Teacher : Person
{
}
这样,公共内容统一写在Person中即可。
3.6.3继承的基本语法
在C#中,使用:表示继承。
cs
class 子类名 : 父类名
{
}
例如:
cs
public class Animal
{
public string Name { get; set; }
public void Eat()
{
Console.WriteLine($"{Name} 正在吃东西。");
}
}
public class Dog : Animal
{
}
这里:Animal是父类,也叫做基类。Dog是子类,也叫做派生类。Dog继承Animal后,就可以直接使用Name和Eat()。
3.6.4子类可以继承什么
子类通常可以继承父类中的字段、属性、方法、事件、受保护成员,但是否能访问,还要看访问修饰符。
常见的情况:
- public:子类可以访问,类外也可以访问
- protected:子类可以访问,类外不能直接访问
- private:只有父类自己能访问,子类不能直接访问
3.6.5子类拓展父类功能
继承的意义不仅在于复用,还在于拓展。
示例
cs
using System;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public void ShowInfo()
{
Console.WriteLine($"姓名:{Name},年龄:{Age}");
}
}
public class Student : Person
{
public string StudentId { get; set; }
public void Study()
{
Console.WriteLine($"{Name} 正在学习。");
}
}
使用实例
cs
Student student = new Student();
student.Name = "张三";
student.Age = 20;
student.StudentId = "S001";
student.ShowInfo();
student.Study();
这里的Student继承了Person的公共成员,又定义了自己的StudentId和Study(),这就是典型的保留共享,拓展个性。
3.6.6protected访问修饰符
在继承中,protected非常常见。
它表示:该成员只能在当前类和子类中访问,不能在类外直接访问。
示例
cs
using System;
public class Animal
{
protected string name;
public void SetName(string name)
{
this.name = name;
}
}
public class Cat : Animal
{
public void Meow()
{
Console.WriteLine($"{name} 正在喵喵叫。");
}
}
使用示例
cs
Cat cat = new Cat();
cat.SetName("小白");
cat.Meow();
这里的name不能在类外直接访问,但子类Cat可以使用它。
3.6.7base关键字
在子类中,可以通过base访问父类成员,它又两个常见的用途,一个是调用父类的方法或属性,另一个是调用父类构造函数。
调用父类成员
cs
using System;
public class Person
{
public string Name { get; set; }
public void ShowInfo()
{
Console.WriteLine($"姓名:{Name}");
}
}
public class Student : Person
{
public void ShowStudentInfo()
{
base.ShowInfo();
Console.WriteLine("这是一个学生对象。");
}
}
调用父类构造函数
cs
using System;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
public class Student : Person
{
public string StudentId { get; set; }
public Student(string name, int age, string studentId) : base(name, age)
{
StudentId = studentId;
}
}
这里的:base(name,age)表示先调用父类构造函数,完成父类部分的初始化。
3.6.8继承与构造函数的关系
创建子类对象时,会先执行父类构造函数,再执行子类构造函数。
示例
cs
using System;
public class Person
{
public Person()
{
Console.WriteLine("父类构造函数执行了。");
}
}
public class Student : Person
{
public Student()
{
Console.WriteLine("子类构造函数执行了。");
}
}
public class Program
{
public static void Main()
{
Student student = new Student();
}
}
输出结果

3.7抽象类
有些父类只是一个更高层次的概念,本身并不适合直接创建对象。
例如,动物时一个抽象概念,图形是一个抽象概念。这些时候就可以使用抽象类。
3.7.1抽象类的概念
抽象类使用abstract关键字修饰。它的主要作用是作为其他类的基类,描述一组共同特征。
特点是不能直接实例化 ,可以包含普通成员 ,可以包含抽象方法 ,抽象方法必须由非抽象子类实现。
3.7.2为什么需要抽象类
普通父类适合表示可以直接存在的对象,例如Person、Book等,但如果父类只是一个规则模板或概念模型,就更适合使用抽象类。
但如果父类只是一个规则模板或概念模型,就更适合使用抽象类。
例如:所有动物都能发出声音,但是不同动物的声音不同。所有图形都可以计算面积,但公式不同。所有员工都需要计算工资,但算法不同。
这种情况下,父类很难给出统一完整的实现,于是就额可以把它定义为抽象类。
3.7.3抽象类的基本语法
cs
public abstract class 类名
{
}
示例
cs
public abstract class Animal
{
public string Name { get; set; }
}
这里Animal是抽象类,不能直接这样创建对象。
cs
// Animal animal = new Animal(); // 错误
但是可以被子类继承。
3.7.4抽象方法
抽象类中可以定义抽象方法,抽象方法只有声明,没有方法体,用abstract修饰。
cs
public abstract void 方法名();
抽象方法的意义是:父类只规定你必须有这个行为,但具体怎么实现,由子类决定。
3.7.5抽象类示例:动物发声
cs
using System;
public abstract class Animal
{
public string Name { get; set; }
public abstract void MakeSound();
public void Eat()
{
Console.WriteLine($"{Name} 正在吃东西。");
}
}
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name} 汪汪叫。");
}
}
public class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name} 喵喵叫。");
}
}
public class Program
{
public static void Main()
{
Dog dog = new Dog { Name = "大黄" };
Cat cat = new Cat { Name = "小白" };
dog.MakeSound();
dog.Eat();
cat.MakeSound();
cat.Eat();
}
}
输出结果

这个例子中,Animal是抽象类,MakeSound()是抽象方法,Eat()是普通方法,Dog和Cat必须实现MakeSound()
3.7.6抽象类和普通类的区别
|-----------|--------|--------|
| 对比项 | 普通类 | 抽象类 |
| 能否实例化 | 可以 | 不可以 |
| 是否能包含普通方法 | 可以 | 可以 |
| 是否能包含抽象方法 | 不可以 | 可以 |
| 主要用途 | 创建具体对象 | 作为基类模板 |
你可以简单理解为,普通类更偏向于直接使用,抽象类更偏向于提供规范和基础能力。
3.8多态
再学习了继承和抽象类之后,就可以进一步理解多态。多态是面向对象三大特性之一。它表示:同一个父类引用,在面对不同子类对象时,可以表现出不同的行为。简单来说就是,看起来调用的是同一个方法,实际执行的却可能是不同子类中的实现。这就是同一接口,多种表现。
3.8.1多态的实现前提
在C#中,常见的运行时多态通常需要满足以下条件:
- 存在继承关系
- 父类方法允许被重写(virtual或abstract)
- 子类重写该方法(override)
- 使用父类引用指向子类对象
3.8.2virtual和override
如果父类中的方法提供了一个默认实现,但运行子类重新定义,就可以使用virtual。
示例
cs
using System;
public class Animal
{
public string Name { get; set; }
public virtual void MakeSound()
{
Console.WriteLine($"{Name} 发出声音。");
}
}
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name} 汪汪叫。");
}
}
public class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name} 喵喵叫。");
}
}
这里父类方法使用virtual,子类方法使用override,这就形成了可重写关系。
3.8.3多态示例:父类引用指向子类对象
cs
using System;
public class Animal
{
public string Name { get; set; }
public virtual void MakeSound()
{
Console.WriteLine($"{Name} 发出声音。");
}
}
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name} 汪汪叫。");
}
}
public class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name} 喵喵叫。");
}
}
public class Program
{
public static void Main()
{
Animal a1 = new Dog { Name = "小黑" };
Animal a2 = new Cat { Name = "小白" };
a1.MakeSound();
a2.MakeSound();
}
}
输出结果

虽然a1和a2的类型都写成了Animal,但运行时会根据实际对象类型调用对应的重写方法。这就是多态最经典的表现。
3.8.4抽象类与多态的结合
实际上,抽象类和多态经常一起出现。原因是抽象类定义统一规范,子类提供具体实现,父类引用统一调用,形成多态效果。
例如前面的Animal,抽象类规定所有动物都必须实现MakeSound(),不同动物分别实现自己的叫声,程序在处理时只关心它是Animal,不必关系它到底是狗、猫还是鸟,这就是面向对象设计中非常典型的一种结构。
3.8.5完整的继承抽象多态案例
下面设计了一个员工工资系统:要求所有员工都有姓名,不同员工的工资计算方式不同,普通员工按固定工资计算,经理公司=基本工资+奖金,程序可以统一处理所有员工。
代码实现
cs
using System;
public abstract class Employee
{
public string Name { get; set; }
public Employee(string name)
{
Name = name;
}
public abstract decimal CalculateSalary();
public void ShowInfo()
{
Console.WriteLine($"员工:{Name},工资:{CalculateSalary()}");
}
}
public class NormalEmployee : Employee
{
public decimal BaseSalary { get; set; }
public NormalEmployee(string name, decimal baseSalary) : base(name)
{
BaseSalary = baseSalary;
}
public override decimal CalculateSalary()
{
return BaseSalary;
}
}
public class Manager : Employee
{
public decimal BaseSalary { get; set; }
public decimal Bonus { get; set; }
public Manager(string name, decimal baseSalary, decimal bonus) : base(name)
{
BaseSalary = baseSalary;
Bonus = bonus;
}
public override decimal CalculateSalary()
{
return BaseSalary + Bonus;
}
}
public class Program
{
public static void Main()
{
Employee[] employees =
{
new NormalEmployee("张三", 5000),
new Manager("李四", 8000, 3000)
};
foreach (Employee employee in employees)
{
employee.ShowInfo();
}
}
}
输出结果

可以试着自己分析,体会继承,多态,抽象类的实际用法。
四、结语
类是C#编程中最重要的知识点之一,也是后续学习接口、泛型、集合、委托、LINQ、ASP.NET等内容的基础。对于初学者来说,学习类不仅仅是记住语法,更重要的是理解它背后的设计思想:封装、继承、多态。
五、参考资料
以下是微软官方提供的 C# 学习资料,适合进一步阅读和查阅:
-
Microsoft Learn - C# 文档首页
https://learn.microsoft.com/zh-cn/dotnet/csharp/ -
Microsoft Learn - C# 编程指南
https://learn.microsoft.com/zh-cn/dotnet/csharp/programming-guide/ -
Microsoft Learn - C# 中的类和对象
https://learn.microsoft.com/zh-cn/dotnet/csharp/fundamentals/tutorials/classes -
Microsoft Learn - 属性(Properties)
https://learn.microsoft.com/zh-cn/dotnet/csharp/programming-guide/classes-and-structs/properties -
Microsoft Learn - 构造函数(Constructors)
https://learn.microsoft.com/zh-cn/dotnet/csharp/programming-guide/classes-and-structs/constructors -
Microsoft Learn - 继承(Inheritance)
https://learn.microsoft.com/zh-cn/dotnet/csharp/fundamentals/object-oriented/inheritance -
Microsoft Learn - 多态(Polymorphism)
https://learn.microsoft.com/zh-cn/dotnet/csharp/fundamentals/object-oriented/polymorphism