C#面向对象编程:类与继承机制深度解读
C#面向对象编程之基础:类的构建与剖析
类的定义与基础结构
在C# 中,类是一种封装数据和相关行为的构造体,它是面向对象编程的核心。类的定义使用class
关键字,例如:
class MyClass
{
// 字段(成员变量)
private int myField;
// 属性
public int MyProperty
{
get { return myField; }
set { myField = value; }
}
// 方法
public void MyMethod()
{
Console.WriteLine("This is MyMethod in MyClass.");
}
}
在上述代码中,MyClass
类包含了一个私有字段myField
,一个公开属性MyProperty
用于访问和修改myField
,以及一个公开方法MyMethod
。字段用于存储数据,属性提供了一种灵活的方式来访问和修改字段,而方法则定义了类可以执行的操作。
访问修饰符
private
:私有访问修饰符意味着只有类内部的代码可以访问该成员。如上述代码中的myField
,外部代码无法直接访问,从而保证了数据的安全性和封装性。public
:公开访问修饰符允许任何代码访问该成员。MyProperty
和MyMethod
使用public
修饰,使得其他类可以通过创建MyClass
的实例来访问它们。protected
:受保护访问修饰符表示只有类本身及其派生类可以访问该成员。这在继承机制中非常重要,我们后续会详细讨论。internal
:内部访问修饰符表示只有在同一个程序集内的代码可以访问该成员。程序集是一个可部署的单元,例如一个.dll
文件或一个.exe
文件。
构造函数与析构函数
- 构造函数:构造函数用于在创建类的实例时初始化对象的状态。构造函数与类名相同,没有返回类型。
class Person
{
public string Name { get; set; }
public int Age { get; set; }
// 构造函数
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
在上述代码中,Person
类有一个接受string
类型的name
和int
类型的age
作为参数的构造函数。当我们创建Person
的实例时,可以使用这个构造函数来初始化对象的属性:
Person person = new Person("John", 30);
- 析构函数:析构函数用于在对象被垃圾回收器回收之前执行清理操作。析构函数不能有参数,也不能被显式调用。
class ResourceHolder
{
// 模拟占用的资源
private IntPtr unmanagedResource;
~ResourceHolder()
{
// 释放非托管资源
if (unmanagedResource != IntPtr.Zero)
{
// 实际的资源释放代码,例如调用本地API
Console.WriteLine("Releasing unmanaged resource.");
}
}
}
在上述代码中,ResourceHolder
类的析构函数在对象即将被回收时释放非托管资源。然而,在现代C#编程中,通常建议使用IDisposable
接口来进行资源管理,这比析构函数更加可控和高效。
深入C#类的特性与高级应用
静态成员
- 静态字段:静态字段属于类本身,而不是类的实例。所有实例共享同一个静态字段。
class Counter
{
// 静态字段
public static int Count { get; set; }
public Counter()
{
Count++;
}
}
在上述代码中,每次创建Counter
的实例时,静态字段Count
都会增加。可以通过类名直接访问静态字段:
Counter counter1 = new Counter();
Counter counter2 = new Counter();
Console.WriteLine(Counter.Count); // 输出 2
- 静态方法:静态方法同样属于类,不需要创建类的实例就可以调用。
class MathUtils
{
public static int Add(int a, int b)
{
return a + b;
}
}
调用静态方法:
int result = MathUtils.Add(3, 5); // result 为 8
嵌套类
嵌套类是在另一个类内部定义的类。嵌套类可以访问外部类的私有成员。
class OuterClass
{
private int outerField = 10;
// 嵌套类
class InnerClass
{
public void InnerMethod()
{
OuterClass outer = new OuterClass();
Console.WriteLine(outer.outerField);
}
}
}
可以通过以下方式使用嵌套类:
OuterClass.InnerClass inner = new OuterClass.InnerClass();
inner.InnerMethod();
密封类
密封类使用sealed
关键字修饰,不能被其他类继承。这在某些情况下非常有用,例如防止某些关键类被错误地继承,或者为了性能优化(因为编译器可以对密封类进行一些特殊的优化)。
sealed class FinalClass
{
// 类的成员
}
如果尝试从FinalClass
继承,编译器会报错。
C#继承机制的全面解析
继承的基本概念与语法
继承是一种机制,通过它一个类(派生类)可以获取另一个类(基类)的成员。在C#中,使用冒号(:
)来表示继承关系。
class Animal
{
public string Name { get; set; }
public void Eat()
{
Console.WriteLine(Name + " is eating.");
}
}
class Dog : Animal
{
public void Bark()
{
Console.WriteLine(Name + " is barking.");
}
}
在上述代码中,Dog
类继承自Animal
类。Dog
类自动获得了Animal
类的Name
属性和Eat
方法,并且还可以定义自己特有的方法,如Bark
。
Dog dog = new Dog();
dog.Name = "Buddy";
dog.Eat(); // 输出 Buddy is eating.
dog.Bark(); // 输出 Buddy is barking.
重写基类成员
- 虚方法与重写:基类中的方法如果希望派生类可以重写,需要使用
virtual
关键字修饰。派生类使用override
关键字来重写该方法。
class Shape
{
public virtual double Area()
{
return 0;
}
}
class Circle : Shape
{
public double Radius { get; set; }
public override double Area()
{
return Math.PI * Radius * Radius;
}
}
在上述代码中,Shape
类的Area
方法是虚方法,Circle
类重写了Area
方法以计算圆的面积。
Circle circle = new Circle { Radius = 5 };
Console.WriteLine(circle.Area()); // 输出圆的面积
- 新方法隐藏:如果派生类定义了与基类中方法同名但不使用
override
关键字的方法,会发生新方法隐藏。这种情况下,派生类的方法会隐藏基类的方法,但通常不推荐使用,因为它可能会导致代码可读性和维护性问题。
class BaseClass
{
public void Method()
{
Console.WriteLine("BaseClass Method");
}
}
class DerivedClass : BaseClass
{
public new void Method()
{
Console.WriteLine("DerivedClass Method");
}
}
BaseClass baseObj = new DerivedClass();
baseObj.Method(); // 输出 BaseClass Method
DerivedClass derivedObj = new DerivedClass();
derivedObj.Method(); // 输出 DerivedClass Method
访问基类成员
base
关键字:在派生类中,可以使用base
关键字来访问基类的成员。例如,在派生类的构造函数中调用基类的构造函数。
class Person
{
public string Name { get; set; }
public Person(string name)
{
Name = name;
}
}
class Student : Person
{
public int StudentId { get; set; }
public Student(string name, int studentId) : base(name)
{
StudentId = studentId;
}
}
在上述代码中,Student
类的构造函数使用base(name)
调用了Person
类的构造函数来初始化Name
属性。
2. 访问基类重写成员:当派生类重写了基类的方法后,仍然可以使用base
关键字访问基类的原始方法。
class Shape
{
public virtual double Area()
{
return 0;
}
}
class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public override double Area()
{
// 调用基类的 Area 方法
double baseArea = base.Area();
return Width * Height;
}
}
继承机制中的多态性实现
编译时多态(静态多态)
编译时多态主要通过方法重载来实现。方法重载是指在同一个类中定义多个同名但参数列表不同的方法。
class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
public double Add(double a, double b)
{
return a + b;
}
}
在上述代码中,Calculator
类有两个Add
方法,一个接受两个int
类型参数,另一个接受两个double
类型参数。编译器会根据调用时传入的参数类型来决定调用哪个方法。
Calculator calculator = new Calculator();
int result1 = calculator.Add(3, 5); // 调用 int 版本的 Add 方法
double result2 = calculator.Add(3.5, 5.5); // 调用 double 版本的 Add 方法
运行时多态(动态多态)
运行时多态通过继承和虚方法重写来实现。当通过基类类型的变量引用派生类对象时,实际调用的方法取决于对象的实际类型,而不是变量的类型。
class Shape
{
public virtual double Area()
{
return 0;
}
}
class Circle : Shape
{
public double Radius { get; set; }
public override double Area()
{
return Math.PI * Radius * Radius;
}
}
class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public override double Area()
{
return Width * Height;
}
}
Shape[] shapes = new Shape[]
{
new Circle { Radius = 5 },
new Rectangle { Width = 4, Height = 6 }
};
foreach (Shape shape in shapes)
{
Console.WriteLine(shape.Area());
}
在上述代码中,shapes
数组包含了Circle
和Rectangle
类型的对象,但通过Shape
类型的变量来引用。在循环中调用Area
方法时,实际调用的是每个对象对应的重写后的Area
方法,这就是运行时多态的体现。
继承关系中的类型转换
隐式转换
- 派生类到基类的隐式转换:由于派生类是基类的一种特殊类型,因此可以将派生类对象隐式转换为基类类型。
class Animal
{
// 类的成员
}
class Dog : Animal
{
// 类的成员
}
Dog dog = new Dog();
Animal animal = dog; // 隐式转换
这种转换是安全的,因为派生类对象包含了基类的所有成员。
显式转换
- 基类到派生类的显式转换:将基类对象转换为派生类对象需要进行显式转换。但是,只有当基类对象实际指向的是派生类对象时,这种转换才会成功,否则会抛出
InvalidCastException
异常。
Animal animal = new Dog();
Dog dog = (Dog)animal; // 显式转换,成功
Animal animal2 = new Animal();
Dog dog2 = (Dog)animal2; // 显式转换,会抛出 InvalidCastException
- 使用
is
和as
关键字:为了避免在显式转换时抛出异常,可以使用is
关键字先检查对象是否可以转换,或者使用as
关键字进行转换,如果转换失败会返回null
而不是抛出异常。
Animal animal3 = new Dog();
if (animal3 is Dog)
{
Dog dog3 = (Dog)animal3;
}
Animal animal4 = new Animal();
Dog dog4 = animal4 as Dog;
if (dog4 != null)
{
// 进行操作
}
继承与接口的综合应用
接口的定义与实现
接口是一种契约,它定义了一组方法签名但不包含方法的实现。类可以实现一个或多个接口,以表明它支持这些接口定义的行为。
interface IPrintable
{
void Print();
}
class Book : IPrintable
{
public string Title { get; set; }
public void Print()
{
Console.WriteLine("Book Title: " + Title);
}
}
在上述代码中,IPrintable
接口定义了一个Print
方法,Book
类实现了这个接口并提供了Print
方法的具体实现。
Book book = new Book { Title = "C# Programming" };
IPrintable printable = book;
printable.Print(); // 输出 Book Title: C# Programming
继承与接口的协同工作
一个类可以在继承另一个类的同时实现接口。这使得代码的复用性和灵活性得到了极大的提升。
class Shape
{
// 类的成员
}
interface IDrawable
{
void Draw();
}
class Circle : Shape, IDrawable
{
public double Radius { get; set; }
public void Draw()
{
Console.WriteLine("Drawing a circle with radius " + Radius);
}
}
在上述代码中,Circle
类继承自Shape
类并实现了IDrawable
接口。这样Circle
类既拥有了Shape
类的特性,又具备了IDrawable
接口定义的绘制功能。
Circle circle = new Circle { Radius = 5 };
IDrawable drawable = circle;
drawable.Draw(); // 输出 Drawing a circle with radius 5
接口继承
接口也可以继承其他接口,形成接口的继承层次结构。
interface IAnimal
{
void Eat();
}
interface IMammal : IAnimal
{
void Nurse();
}
class Dog : IMammal
{
public void Eat()
{
Console.WriteLine("Dog is eating.");
}
public void Nurse()
{
Console.WriteLine("Dog is nursing its puppies.");
}
}
在上述代码中,IMammal
接口继承自IAnimal
接口,Dog
类实现了IMammal
接口,因此需要实现IAnimal
接口的Eat
方法和IMammal
接口的Nurse
方法。
通过深入理解C#中的类与继承机制,开发者能够编写出更加模块化、可维护和可扩展的代码,充分发挥面向对象编程的优势。无论是小型应用程序还是大型企业级项目,这些概念都是构建稳健软件的基石。