继承的类型
- 单重继承:表示一个类可以派生自一个基类。C#就采用这种继承。
- 多重继承:多重继承允许一个类派生自多个类。C#不支持类的多重继承,但允许接口的多重继承。
- 多层继承:多层继承允许继承有更大的层次结构。类B派生自类A。类C又派生自类B。其中,类B也称为中间基类,C#支持它,也很常用
- 接口继承:定义了接口的继承。这里允许多重继承。
实现继承
- 声明派生自另一个类的一个类,可以用下列语法:
class MyDerivedClass:MyBaseClass { // }
- 如果类也派生自接口,则用逗号分隔列表中的基类和接口: ``` public class MyDerivedClass:MyBaseClass,IInterfacel,IInterface2 { // }
- 如果类和接口都用于派生,则类总是必须放在接口的前面。 - 对于结构,语法如下(只能用于接口继承):
public struct MyDerivedStruct:IInterfacel,IInterface2 {
//
}
- 如果在类定义中没有指定的基类,C#编译器就假定System.Object 是基类。因此,派生自Object类(或使用object关键字),与不定义基类的效果是相同的。
class MyClass //implicitly derives from System.Object {
}
举个例子,无论是矩形还是椭圆,形状都有一些共同点;形状都有位置和大小。定义相应的类时,位置和大小应包含在Shape类中。Shape类定义了只读属性Position和Shape,它们使用自动属性初始化器来初始化
public class Position {
public int X{get;set;} public int Y{get;set;}
} public class Size {
public int Width{get;set;} public int Heigh{get;set;}
} public class Shape {
public Position Position{get;}=new Position(); public Size Size{get;}=new Size();
}
虚方法 - 把一个基类方发声明为virtual,就可以在任何派生类中重写该方法
public class Shape {
public virtual void Draw() { WriteLine($"Shape with {Position}and{size}"); }
}
- 也可以把virtual 关键字和表达式体的方法(使用Lamda运算符)一起使用。这个语法可以独立修饰符,单独使用
public class Shape {
public virtual void Draw()=>WriteLine($"Shape with {Position}and{size}")
}
- 也可以把属性声明为virtual。对于虚属性或重写属性,语法与非虚属性相同,但要在定义中添加关键字virtual,其语法如下
public virtual Size Size{get;set;} 可以给虚属性使用完整的属性语法: private Size _size; public virtual Size Size {
get { return _size; } set { _size=value; }
}
- Size和Position类型重写了ToString()方法。这个方法在基类Object中声明virtual:
public class Position {
public int X{get;set;} public int Y{get;set;} public override string ToString()=>$"X:{X},Y:{Y}";
} public class Size {
public int Width{get;set;} public int Height{get;set;} public override string ToString()=>$"Width:{Width},Height:{Height}";
}
is和as运算符 - is和as是与继承有关的重要运算符 using - using关键字在C#中用多个用法,using声明用于导入名称空间。using语句处理实现IDisposable的对象,并在作用域的末尾用Dispose方法。 实现IDisposable接口和析构函数 - 利用运行库强制执行的析构函数,但析构函数的执行是不确定的,而且,由于垃圾回收器的工作方式,它会给运行库增加不可接受的系统开销。 - IDisposable 接口提供一种机制,该机制允许类的用户控制释放资源的时间,但需要确保调用Dispose()方法。 - 如果创建了终结器,就应该实现IDisposable接口。 - 双重实现的例子
using System; public class ResourceHolder:IDisposable {
private bool _isDisposed=false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protect virtual void Dispose(bool disposing) { if(!_isDisposed) { if(disposing) { } } } _isDisposed=true;
} -ResourceHolder() {
Dispose(false);
} public void SomeMethod() {
if(_isDisposed) { throw new ObjectDisposedException("ResourceHolder"); }
} Dispose()方法有第二个protect的重载方法,它带一个布尔参数,这是真正完成清理工作的方法。Dispose(bool)方法由析构函数和IDisposable.Dispose()方法调用。 ```
IDisposable和终结器的规则
- 如果类定义了实现IDisposable的成员,该类也应该实现IDisposable
- 实现IDisposable并不意味着也应该实现一个终结器。终结器会带来额外的开销,因为它需要创建一个对象,释放该对象的内存,需要GC的额外处理。只在需要时才应该实现终结器,例如,发布本机资源。要释放本机资源,就需要终结器
- 如果实现了终结器,也应该实现IDisposable接口。这样,本机资源可以早些释放,而不仅实在GC找出被占用的资源时,才释放资源。
- 在终结器的实现代码中,不能访问已终结的对象。终结器的执行顺序是没有保证的
- 如果所使用的一个对象实现了IDisposable接口,就不再需要对象时调用Dispose方法。如果在方法中使用这个对象,using语句比较方便。如果对象是类的一个成员,就让类也实现IDisPOSable。