声明
本文内容来自微软 MVP solenovex 的视频教程——真会C#?- 第3章 创建类型,大致和第 5 课—— 继承 - 继承、多态、引用转换,virtual 对应。可在 GitHub 中查看 C# 视频教程的配套PPT
本文主要包括以下内容:
- 继承
- 多态
- 引用转换
- virtual
继承
一个类可以继承另一个类,从而对原有类进行扩展和自定义,可以叫做子类和父类。继承的类让你可以重用被继承类的功能。C# 里,一个类只能继承于一个类,但是这个类却可以被多个类继承。
public class Asset
{
public string Name;
}
public class Stock : Asset // inherits from Asset
{
public long SharesOwned;
}
public class House : Asset // inherits from Asset
{
public decimal Mortgage;
}
Stock msft = new Stock { Name="MSFT",
SharesOwned=1000 };
Console.WriteLine (msft.Name); // MSFT
Console.WriteLine (msft.SharesOwned); // 1000
House mansion = new House { Name="Mansion",
Mortgage=250000 };
Console.WriteLine (mansion.Name); // Mansion
Console.WriteLine (mansion.Mortgage); // 250000
多态
引用是多态的,类型为x的变量可以引用其子类的对象。
public static void Display (Asset asset)
{
System.Console.WriteLine (asset.Name);
}
Stock msft = new Stock ... ;
House mansion = new House ... ;
Display (msft);
Display (mansion);
因为子类具有父类的全部功能特性,所以参数可以是子类,反过来则不行。
static void Main() { Display (new Asset()); } // Compile-time error
public static void Display (House house) // Will not accept Asset
{
System.Console.WriteLine (house.Mortgage);
}
引用转换
一个对象的引用可以隐式的转换到其父类的引用(向上转换),想转换到子类的引用则需要显式转换(向下转换),引用转换:创建了一个新的引用,它也指向同一个对象。
向上转换
从子类的引用创建父类的引用。
Stock msft = new Stock();
Asset a = msft; // Upcast
变量a依然指向同一个 Stock 对象(msft 也指向它)。Console.WriteLine (a == msft); // True
尽管变量a和msft指向同一个对象,但是a的可视范围更小一些。
Console.WriteLine (a.Name); // OK
Console.WriteLine (a.SharesOwned); // Error: SharesOwned undefined
向下转换
从父类的引用创建出子类的引用。
Stock msft = new Stock();
Asset a = msft; // Upcast
Stock s = (Stock)a; // Downcast
Console.WriteLine (s.SharesOwned); // <No error>
Console.WriteLine (s == a); // True
Console.WriteLine (s == msft); // True
和向上转换一样,只涉及到引用,底层的对象不会受影响,需要显式转换,因为可能会失败。如果向下转换失败,那么会抛出 InvalidCastException(属于运行时类型检查)。
House h = new House();
Asset a = h; // Upcast always succeeds
Stock s = (Stock)a; // Downcast fails: a is not a Stock
as 操作符
as 操作符会执行向下转换,如果转换失败,不会抛出异常,值会变为 null。
Asset a = new Asset();
Stock s = a as Stock; // s is null; no exception thrown
as 操作符无法做自定义转换。long x = 3 as long; // Compile-time error
is 操作符
is 操作符会检验引用的转换是否成功。换句话说,判断对象是否派生于某个类(或者实现了某个接口)。通常用于向下转换前的验证:
if (a is Stock s)
Console.WriteLine (s.SharesOwned);
如果拆箱转换可以成功的话,那么使用 is 操作符的结果会是 true。
is 操作符和模式变量
C# 7里,在使用 is 操作符的时候,可以引入一个变量。
Stock s;
if (a is Stock)
{
s = (Stock) a;
Console.WriteLine (s.SharesOwned);
}
引入的变量可以立即“消费”。
if (a is Stock s && s.SharesOwned > 100000)
Console.WriteLine ("Wealthy");
if (a is Stock s && s.SharesOwned > 100000)
Console.WriteLine ("Wealthy");
else
s = new Stock(); // s is in scope
Console.WriteLine (s.SharesOwned); // Still in scope
Virtual 函数成员
标记为 Virtual 的函数可以被子类重写,包括方法、属性、索引器、事件。
public class Asset
{
public string Name;
public virtual decimal Liability => 0; // Expression-bodied property
}
override 重写
使用 override 修饰符,子类可以重写父类的函数。
public class Stock : Asset
{
public long SharesOwned;
}
public class House : Asset
{
public decimal Mortgage;
public override decimal Liability => Mortgage;
}
House mansion = new House { Name="McMansion", Mortgage=250000 };
Asset a = mansion;
Console.WriteLine (mansion.Liability); // 250000
Console.WriteLine (a.Liability); // 250000
virtual方法和重写方法的签名、返回类型、可访问程度必须是一样的,重写方法里使用 base 关键字可以调用父类的实现。
注意:在构造函数里调用 virtual 方法可能比较危险,因为编写子类的开发人员可能不知道他们在重写方法的时候,面对的是一个未完全初始化的对象。换句话说,重写的方法可能会访问依赖于还未被构造函数初始化的字段的属性或方法。
继承参考
Inheritance (C# Programming Guide)
网友评论