当前位置: 首页 > news >正文

菜鸟的C#学习(二)

文章目录

  • 一、类的访问
    • 1、普通类继承抽象类
    • 2、普通类继承抽象类,抽象类继承接口,三者联系
  • 二、类中方法的访问
    • 2.1 抽象方法和虚方法
    • 2.2 虚方法和普通方法
      • **1. 调用机制**
      • **2. 方法重写**
      • **3. 设计意图**
      • **4. 性能差异**
      • **5. 语法对比表**
      • **总结:何时使用?**

一、类的访问

在这里插入图片描述

1、普通类继承抽象类

在C#里,普通类继承抽象类时,有以下这些要点需要留意:

1. 必须实现所有抽象成员
抽象类中的抽象方法和属性不具备实现代码,继承它的普通类得把这些抽象成员全部实现出来。实现时,方法签名要和抽象类中定义的保持一致,并且要用override关键字。

public abstract class Shape
{public abstract double Area(); // 抽象方法
}public class Circle : Shape
{public double Radius { get; set; }// 实现抽象方法public override double Area() => Math.PI * Radius * Radius;
}

2. 遵循访问修饰符的限制
在实现抽象成员时,访问修饰符要和抽象类中定义的一样。比如,抽象类里的抽象方法是protected,那么派生类中实现该方法时也得用protected

3. 不能直接实例化抽象类
抽象类没办法直接创建实例,必须通过派生类来实例化。

Shape shape = new Circle { Radius = 5 }; // 正确
Shape shape = new Shape(); // 错误,无法实例化抽象类

4. 可以添加新成员
继承抽象类的普通类能够新增自己的字段、属性、方法或者事件。

public class Rectangle : Shape
{public double Width { get; set; }public double Height { get; set; }public override double Area() => Width * Height;// 新增方法public double Perimeter() => 2 * (Width + Height);
}

5. 抽象类可以包含非抽象成员
抽象类中除了抽象成员,还能有已经实现的方法、属性等,派生类可以直接继承或者重写这些非抽象成员。

public abstract class Animal
{public string Name { get; set; }public void Eat() => Console.WriteLine($"{Name} is eating."); // 非抽象方法public abstract void MakeSound(); // 抽象方法
}public class Dog : Animal
{public override void MakeSound() => Console.WriteLine("Woof!");
}

6. 抽象类也能继承自其他类或抽象类
如果抽象类继承了另一个抽象类,它可以选择实现部分抽象成员,剩下的由派生类去实现。

public abstract class Vehicle
{public abstract void Start();
}public abstract class Car : Vehicle
{public override void Start() => Console.WriteLine("Car started."); // 实现基类的抽象方法public abstract void Drive(); // 定义新的抽象方法
}public class SportsCar : Car
{public override void Drive() => Console.WriteLine("Sports car is driving fast.");
}

7. 不能用 sealed 修饰派生类
因为普通类要实现抽象类的抽象成员,所以不能用sealed关键字修饰该普通类,不然就没办法被其他类继承了。

总结
普通类继承抽象类时,要实现所有抽象成员,遵循访问修饰符的规则,不能实例化抽象类,不过可以添加新成员。抽象类可以有非抽象成员,还能继承其他类或抽象类。

2、普通类继承抽象类,抽象类继承接口,三者联系

当一个类(派生类)继承抽象基类,而抽象基类又实现了接口时,三者的成员函数关系遵循以下规则(以C#为例):

1. 接口定义“契约”,抽象基类部分或全部实现,派生类完成剩余实现

  • 接口:定义必须实现的成员(如方法、属性),但不提供实现。
  • 抽象基类
    • 必须“声明”实现接口的所有成员(即使只实现部分)。
    • 可将部分接口成员标记为 abstract(延迟到派生类实现),其他成员提供具体实现。
  • 派生类
    • 必须实现抽象基类中标记为 abstract 的接口成员(若有)。
    • 可选择重写(override)抽象基类中已实现的接口成员(若为 virtual)。

2. 示例说明
假设存在以下结构:

// 接口定义
public interface IMyInterface
{void MethodA();  // 接口方法void MethodB();
}// 抽象基类实现接口
public abstract class MyAbstractBase : IMyInterface
{public void MethodA()  // 具体实现接口方法{Console.WriteLine("Base.MethodA");}public abstract void MethodB();  // 抽象方法,延迟到派生类实现
}// 派生类继承抽象基类
public class MyDerivedClass : MyAbstractBase
{public override void MethodB()  // 实现抽象基类的抽象方法{Console.WriteLine("Derived.MethodB");}
}

成员关系分析

  • 接口 IMyInterface:定义 MethodA()MethodB()
  • 抽象基类 MyAbstractBase
    • 实现 MethodA(),派生类可直接使用。
    • MethodB() 标记为 abstract,强制派生类实现。
  • 派生类 MyDerivedClass
    • 无需关心 MethodA()(已由基类实现)。
    • 必须实现 MethodB(),否则会编译错误。

3. 特殊情况:抽象基类未完全实现接口
若抽象基类未实现接口的所有成员(即部分接口成员未被标记为 abstract 且未提供实现),则会导致编译错误。例如:

public abstract class MyAbstractBase : IMyInterface
{// 错误:未实现 MethodB(),且未声明为 abstractpublic void MethodA() { }
}

修正方式

  • MethodB() 声明为 abstract(如示例所示)。
  • 或在抽象基类中提供 MethodB() 的具体实现。

4. 接口显式实现与隐式实现
抽象基类可选择显式实现接口(只能通过接口类型调用):

public abstract class MyAbstractBase : IMyInterface
{void IMyInterface.MethodA()  // 显式实现接口方法{Console.WriteLine("Explicit implementation");}public abstract void MethodB();
}

此时,派生类需通过接口类型调用 MethodA()

MyDerivedClass derived = new MyDerivedClass();
((IMyInterface)derived).MethodA();  // 必须转型为接口类型

5. 派生类重写基类的实现
若抽象基类的方法为 virtual,派生类可选择重写:

public abstract class MyAbstractBase : IMyInterface
{public virtual void MethodA() { }  // 虚拟方法public abstract void MethodB();
}public class MyDerivedClass : MyAbstractBase
{public override void MethodA() { }  // 重写基类方法public override void MethodB() { }  // 实现抽象方法
}

6. 多层继承的扩展
若存在多层继承(如抽象基类继承自另一个抽象基类),规则相同:

  • 每个抽象基类可实现部分接口成员,剩余抽象成员由最终派生类实现。
  • 示例:
    public interface IMyInterface { void MethodA(); }
    public abstract class Base1 : IMyInterface { public abstract void MethodA(); }
    public abstract class Base2 : Base1 { }  // 未实现 MethodA(),仍为抽象类
    public class Derived : Base2 { public override void MethodA() { } }  // 最终实现
    

总结

角色对接口成员的责任对抽象成员的责任
接口定义所有成员签名
抽象基类必须声明实现所有接口成员(部分或全部实现)可定义抽象成员,强制派生类实现
派生类实现抽象基类中未实现的接口成员(即抽象成员)必须实现基类的所有抽象成员

这种分层设计允许:

  • 接口 定义统一契约。
  • 抽象基类 复用通用逻辑,简化派生类实现。
  • 派生类 专注于核心差异化逻辑。

二、类中方法的访问

在这里插入图片描述

2.1 抽象方法和虚方法

在C#中,抽象方法虚方法都用于实现多态性,但它们的设计目的和使用方式有本质区别。以下是两者的核心差异:

1. 定义语法与强制实现

抽象方法虚方法
使用 abstract 关键字声明,且不能有方法体
csharp<br>public abstract void Print();<br>
使用 virtual 关键字声明,必须有默认实现
csharp<br>public virtual void Print() { Console.WriteLine("Base"); }<br>
必须由派生类实现,否则派生类必须声明为抽象类。派生类可以选择是否重写,不重写时将继承基类的默认实现。

2. 所在类的限制

  • 抽象方法:只能存在于抽象类中(即使用 abstract 修饰的类)。
  • 虚方法:可以存在于普通类抽象类中。

3. 重写要求

抽象方法虚方法
派生类必须使用 override 关键字实现,且不能使用 newsealed 隐藏基类方法派生类使用 override 关键字重写(推荐),或使用 new 关键字隐藏基类方法(不推荐)。
示例:
csharp<br>public override void Print() { ... }<br>
示例:
csharp<br>public override void Print() { ... } // 重写<br>public new void Print() { ... } // 隐藏(不推荐)<br>

4. 设计目的

  • 抽象方法:用于定义必须由子类实现的契约,基类只规定方法签名,不提供默认行为。例如:
    public abstract class Shape
    {public abstract double Area(); // 所有形状必须计算面积
    }
    
  • 虚方法:用于提供可扩展的默认行为,允许子类在需要时修改实现。例如:
    public class Animal
    {public virtual void Speak() { Console.WriteLine("Animal sound"); }
    }public class Dog : Animal
    {public override void Speak() { Console.WriteLine("Woof"); } // 可选重写
    }
    

5. 调用方式

  • 抽象方法:无法直接调用,必须通过派生类的实现调用。
  • 虚方法:可以直接通过基类调用默认实现,也可以通过派生类调用重写后的实现。

总结对比表

特性抽象方法虚方法
方法体不能有方法体必须有默认实现
所在类必须在抽象类中可以在普通类或抽象类中
强制实现派生类必须实现派生类可选重写
关键字abstract + overridevirtual + override(可选)
设计意图定义必须实现的契约提供可扩展的默认行为

示例代码

// 抽象类 + 抽象方法
public abstract class Vehicle
{public abstract void Start(); // 必须由子类实现
}// 普通类 + 虚方法
public class Car : Vehicle
{public override void Start() { Console.WriteLine("Car started"); } // 实现抽象方法public virtual void Drive() { Console.WriteLine("Driving normally"); } // 虚方法,提供默认行为
}// 派生类重写虚方法
public class SportsCar : Car
{public override void Drive() { Console.WriteLine("Driving fast!"); } // 重写虚方法
}

何时使用?

  • 使用抽象方法:当基类无法提供有意义的默认实现,且所有子类必须强制实现某个行为时。
  • 使用虚方法:当基类可以提供默认行为,但子类可能需要自定义实现时。

通过合理使用抽象方法和虚方法,可以构建出灵活且易于扩展的面向对象系统。

2.2 虚方法和普通方法

在C#中,虚方法virtual)和普通方法(无修饰符)的核心区别在于是否支持运行时多态。以下是两者的详细对比:

1. 调用机制

虚方法普通方法
使用 virtual 关键字声明,支持运行时多态。基类的虚方法可以在派生类中被override重写。调用时,会根据对象的实际类型决定执行哪个版本的方法。没有特殊修饰符,不支持运行时多态。调用时,根据对象的声明类型决定执行的方法,无论对象的实际类型是什么。
示例
csharp<br>public class Animal {<br> public virtual void Speak() { Console.WriteLine("Animal"); }<br>}<br><br>public class Dog : Animal {<br> public override void Speak() { Console.WriteLine("Dog"); }<br>}<br><br>// 输出:Dog<br>Animal animal = new Dog();<br>animal.Speak(); // 调用Dog的实现<br>
示例
csharp<br>public class Animal {<br> public void Speak() { Console.WriteLine("Animal"); }<br>}<br><br>public class Dog : Animal {<br> public new void Speak() { Console.WriteLine("Dog"); } // 使用new隐藏基类方法(不推荐)<br>}<br><br>// 输出:Animal<br>Animal animal = new Dog();<br>animal.Speak(); // 调用Animal的实现<br>

2. 方法重写

虚方法普通方法
可以被派生类使用 override 关键字重写,从而改变方法的行为。不能被重写,但可以使用 new 关键字隐藏基类方法(但这不是真正的重写,只是创建了一个同名的新方法)。
正确做法
csharp<br>public class Base {<br> public virtual void Print() { ... }<br>}<br><br>public class Derived : Base {<br> public override void Print() { ... } // 重写虚方法<br>}<br>
错误做法(隐藏而非重写):
csharp<br>public class Base {<br> public void Print() { ... }<br>}<br><br>public class Derived : Base {<br> public new void Print() { ... } // 隐藏基类方法(编译警告)<br>}<br>

3. 设计意图

虚方法普通方法
用于实现多态性,允许基类定义通用行为,派生类根据需要自定义实现。例如:
csharp<br>public class Shape {<br> public virtual double Area() => 0;<br>}<br><br>public class Circle : Shape {<br> public override double Area() => Math.PI * Radius * Radius;<br>}<br>
用于实现固定行为,不希望派生类修改方法逻辑。例如:
csharp<br>public class Calculator {<br> public int Add(int a, int b) => a + b; // 不需要重写的方法<br>}<br>

4. 性能差异

  • 虚方法:调用时需要通过虚函数表(VTable)动态查找实际要执行的方法,因此性能略低(但在大多数场景下可以忽略不计)。
  • 普通方法:调用时直接绑定到声明类型的方法,性能更高

5. 语法对比表

特性虚方法普通方法
关键字virtual
能否重写能(使用 override不能(只能用 new 隐藏)
多态支持运行时多态(根据对象实际类型)编译时绑定(根据声明类型)
默认行为基类提供默认实现,可被覆盖行为固定,不可被派生类修改
性能略低(通过VTable查找)更高(直接调用)

总结:何时使用?

  • 使用虚方法
    • 当基类希望派生类能够自定义某个方法的实现时。
    • 需要通过基类引用调用派生类方法(实现多态)。
  • 使用普通方法
    • 当方法的逻辑不需要被派生类修改时。
    • 性能敏感的场景(如高频调用的方法)。

通过合理使用虚方法和普通方法,可以在保证代码灵活性的同时,避免不必要的性能开销。

http://www.xdnf.cn/news/1110241.html

相关文章:

  • TDengine 使用最佳实践(1)
  • hot100链表(1)
  • 工业软件出海的ERP-PLM-MES一体化解决方案
  • 自动化运维工具jenkins问题
  • AI 时代的分布式多模态数据处理实践:我的 ODPS 实践之旅、思考与展望
  • 单细胞分析教程 | (二)标准化、特征选择、降为、聚类及可视化
  • 牛客网50题
  • 第14次课 认识图 A
  • docker镜像原理与镜像制作优化
  • Classifier guidance与Classifier-free guidance的原理和公式推导
  • 【STM32实践篇】:最小系统组成
  • 深入详解:决策树在医学影像领域心脏疾病诊断的应用及实现细节
  • Pytest 跳过测试技巧:灵活控制哪些测试该跑、哪些该跳过
  • 图像扭曲增强处理流程
  • 物联网设备数据驱动3D模型的智能分析与预测系统
  • frp内网穿透教程及相关配置
  • 【Redis实战】Widnows本地模拟Redis集群的2种方法
  • Git 相关的常见面试题及参考答案
  • 国产电钢琴电子琴手卷钢琴对比选购指南
  • 2025年亚太杯(中文赛项)数学建模B题【疾病的预测与大数据分析】原创论文讲解(含完整python代码)
  • ESP32使用freertos更新lvgl控件内容
  • 搭建云手机教程
  • 聊下easyexcel导出
  • Java可变参数
  • 从基础加热到智能生态跨越:艾芬达用创新重构行业价值边界!
  • Go mod 依赖管理完全指南:从入门到精通
  • 代码随想录day28贪心算法2
  • 【AI News | 20250711】每日AI进展
  • Spring(四) 关于AOP的源码解析与思考
  • Java SE--抽象类和接口