C# 委托 && Virtual, abstract, override && 静态构造函数

本文深入探讨了C#中的委托概念及其应用场景,并对比了虚拟方法、抽象方法与接口的区别,同时介绍了静态构造函数的用途及特点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.委托

一般来说我们都是针对数据来做操作,但是有时候需要针对方法来操作,这个时候就用到了委托。

委托将方法当作参数,当委托的返回值是void时,系统会先假定这是个多播委托。所谓多播委托,即多个方法都委托给了同一个委托类,用关键字(+=)。这个时候激活委托,这些方法会依照被委托的顺序而执行。可以取消委托,用关键字(-=)。

 

2.Virtual, abstract, override

Virtual是成员的修饰符,即虚函数,不能用来修饰静态方法和类,因为没有意义。被virtual修饰的方法表明希望被派生类来override, 而静态方法无法override. 基于同样的理由,private 和override 和 abstract不能和virtual一起来修饰方法。

 

abstract可以修饰方法和类,即抽象方法和抽象类。实际上一旦abstract修饰了一个方法,那么该类也必须用abstract来修饰,即所谓的包含抽象方法的类一定是抽象类,但是需要注意的是抽象类不一定包含抽象方法。

抽象方法是没有方法体的,派生类可以通过override关键字来实现它。

抽象方法看起来和接口比较类似,但是抽象类也有自己存在的意义:

(1)首先抽象类是对“对象”的抽象,而接口是一种行为规范。

(2)抽象类可以实现部分方法或者全部方法,而接口不能实现任何方法

(3)抽象类的方法的权限可以是任意的,而接口必须是public

(4)抽象类的派生类不一定需要实现抽象类的所有方法,而实现接口的类必须实现接口所有的方法

(5)抽象类的成员变量不限权限,而接口的成员变量一定是staticfinal的

一般来说,接口是为了实现多重继承的,抽象类不能被多成继承,但是接口可以。

 

不能override没有修饰符virtual或者abstract的成员,因为类的成员默认是非虚的。

 

3.静态构造函数

很少用到的概念,典型用途是,当类使用日志文件时,将使用静态构造函数向日志文件中写入数据。

另外也可以根据其特性来做一些只做一次的事情,例如导入库文件之类。

静态构造函数是被系统调用的,程序员无法手动调用。在创建第一个类的实例或者引用任何静态成员之前,系统自动调用。该函数最多只能被调用一次,在类中也最多只有一个,又因为不能被继承,所以和无参的构造函数不冲突。静态构造函数没有访问修饰符和参数。

如果程序员没有写静态构造函数,而类中包含带初始值设定的静态成员的话,编译器会自动生成一个默认的静态构造函数。

 

<think>在C#中,构造函数不能被重写(override),因为构造函数不是继承的。每个类(包括派生类)都必须定义自己的构造函数。但是,可以通过使用`base`关键字来调用基类的构造函数,从而实现构造函数的链式调用。此外,在派生类中,我们可以通过不同的参数列表来重载构造函数,但这并不是重写。 ### 关键点: 1. **构造函数不能被重写**:因为构造函数与类同名,而每个类都有自己的构造函数,所以子类不能重写基类的构造函数。 2. **构造函数继承**:C#构造函数不会被继承。如果派生类没有显式定义构造函数编译器会生成一个默认的无参构造函数,该构造函数会隐式调用基类的无参构造函数。 3. **调用基类构造函数**:在派生类的构造函数中,可以使用`base`关键字来调用基类的某个构造函数。这必须在派生类构造函数体的前面(使用构造函数初始化器)。 4. **构造函数重载**:在同一个类中,可以定义多个构造函数(参数列表不同),这称为构造函数重载。 ### 示例代码: ```csharp using System; // 基类 public class Person { public string Name { get; set; } public int Age { get; set; } // 基类无参构造函数 public Person() { Console.WriteLine("基类无参构造函数被调用"); } // 基类有参构造函数 public Person(string name, int age) { Name = name; Age = age; Console.WriteLine($"基类有参构造函数被调用: {Name}, {Age}"); } } // 派生类 public class Student : Person { public string StudentId { get; set; } // 派生类无参构造函数会隐式调用基类无参构造函数 public Student() { Console.WriteLine("派生类无参构造函数被调用"); } // 派生类有参构造函数,调用基类有参构造函数 public Student(string name, int age, string studentId) : base(name, age) { StudentId = studentId; Console.WriteLine($"派生类有参构造函数被调用: {StudentId}"); } // 另一个有参构造函数,调用基类无参构造函数 public Student(string studentId) : base() { StudentId = studentId; Console.WriteLine($"派生类另一个有参构造函数被调用: {StudentId}"); } } class Program { static void Main() { // 调用派生类无参构造函数 Console.WriteLine("创建学生1:"); Student student1 = new Student(); Console.WriteLine(); // 调用派生类有参构造函数(带三个参数) Console.WriteLine("创建学生2:"); Student student2 = new Student("张三", 20, "S1001"); Console.WriteLine(); // 调用派生类有参构造函数(带一个参数) Console.WriteLine("创建学生3:"); Student student3 = new Student("S1002"); } } ``` ### 输出: ``` 创建学生1: 基类无参构造函数被调用 派生类无参构造函数被调用 创建学生2: 基类有参构造函数被调用: 张三, 20 派生类有参构造函数被调用: S1001 创建学生3: 基类无参构造函数被调用 派生类另一个有参构造函数被调用: S1002 ``` ### 解释: 1. **基类构造函数**: - `Person`类有两个构造函数:一个无参,一个有参。 2. **派生类构造函数**: - `Student`类有三个构造函数: - 无参构造函数:隐式调用基类无参构造函数(如果没有显式使用`base`,则默认调用基类无参构造函数)。 - 带三个参数的构造函数:使用`base(name, age)`显式调用基类的有参构造函数。 - 带一个参数的构造函数:使用`base()`显式调用基类的无参构造函数(也可以省略,因为默认就是调用无参的)。 3. **构造函数调用顺序**: - 创建派生类对象时,先调用基类构造函数,再调用派生类构造函数。 ### 注意事项: - 如果基类没有无参构造函数(例如,只定义了一个有参构造函数),那么派生类必须使用`base`关键字显式调用基类的某个有参构造函数,否则会编译错误。 - 构造函数不能被标记为`virtual`、`abstract`或`override`,因为这些修饰符用于继承成员,而构造函数不会被继承。 ### 结论: C#构造函数不能被重写,但可以通过在派生类中使用`base`关键字来调用基类的构造函数,并且可以在同一个类中重载构造函数。 §§相关问题§§: 1. C#中如何实现构造函数链式调用(在同一类中)? 2. 在派生类中调用基类构造函数的语法规则是什么? 3. 如果基类没有无参构造函数,派生类应该如何定义构造函数? 4. C#静态构造函数的特点和使用场景是什么?</think>### C#构造函数是否能重写? 在C#中,**构造函数不能被重写**。这是面向对象设计中的一个重要原则。以下是详细解释和替代方案: #### 为什么构造函数不能重写? 1. **构造函数的特殊性**: - 构造函数用于创建类的实例 - 每个类都需要自己的构造函数来初始化状态 - 构造函数名称必须与类名相同 2. **继承机制限制**: - 子类不会继承父类的构造函数 - 每个类必须定义自己的构造函数 - 重写(override)只适用于虚方法和虚属性 #### 替代方案:使用基类构造函数调用 虽然不能重写,但可以通过`base`关键字调用基类构造函数: ```csharp using System; // 基类 public class Person { public string Name { get; } public int Age { get; } // 基类构造函数 public Person(string name, int age) { Name = name; Age = age; Console.WriteLine($"Person 构造函数: {Name}"); } } // 派生类 public class Student : Person { public string StudentId { get; } // 通过base调用基类构造函数 public Student(string name, int age, string studentId) : base(name, age) // 必须放在派生类构造函数前 { StudentId = studentId; Console.WriteLine($"Student 构造函数: {StudentId}"); } } class Program { static void Main() { // 创建Student实例 var student = new Student("张三", 20, "S12345"); /* 输出: Person 构造函数: 张三 Student 构造函数: S12345 */ } } ``` #### 关键特性说明: 1. **构造函数链**: - 使用`: base(参数)`调用基类构造函数 - 必须放在派生类构造函数声明之后,方法体之前 2. **执行顺序**: ```mermaid graph LR A[派生类构造函数调用] --> B[基类构造函数执行] B --> C[派生类构造函数体执行] ``` 3. **默认行为**: - 如果不显式调用基类构造函数 - 编译器会自动调用基类的无参构造函数 - 如果基类没有无参构造函数,会编译错误 #### 特殊构造函数类型: 1. **静态构造函数**: ```csharp public class MyClass { static MyClass() { // 类首次使用时执行一次 } } ``` 2. **私有构造函数**: ```csharp public class Singleton { private Singleton() {} // 防止外部实例化 public static Singleton Instance { get; } = new Singleton(); } ``` 3. **记录类型(record)构造函数**: ```csharp public record Student(string Name, int Age); // 编译器自动生成 ``` #### 最佳实践: 1. 当需要扩展初始化逻辑时: ```csharp public class InternationalStudent : Student { public string Country { get; } public InternationalStudent(string name, int age, string id, string country) : base(name, age, id) { Country = country; // 添加额外初始化逻辑 } } ``` 2. 使用工厂方法替代复杂构造: ```csharp public class StudentFactory { public static Student CreateWithDefaults(string name) { return new Student(name, 18, "TEMP_ID"); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值