C# 试题 09:继承与多态
0161 以下代码输出什么?
难度: 实战
class A
{
public virtual string M() => "A";
}
class B : A
{
public override string M() => "B";
}
A a = new B();
Console.WriteLine(a.M());
- A.
A - B.
AB - C.
B,override 按实际类型动态分派 - D. 编译失败
查看答案与解析
正确答案C
解析: override 改写虚槽,调用由对象实际类型决定;a 虽声明为 A,实际指向 B,分派到 B.M 输出 B。
关键点: 虚调用看运行时类型,不看引用静态类型。
0162 以下代码输出什么?
难度: 实战
class A
{
public string M() => "A";
}
class B : A
{
public new string M() => "B";
}
A a = new B();
B b = new B();
Console.WriteLine(a.M() + b.M());
- A.
BB - B.
AB,new 隐藏按编译期静态类型选择 - C.
AA - D. 编译失败
查看答案与解析
正确答案B
解析: new 只是隐藏基成员:通过 A 引用调用选中 A.M,通过 B 引用调用选中 B.M,输出 AB。
关键点: new 是编译期静态绑定,与 override 的运行时分派完全不同。
0163 以下代码输出什么?
难度: 进阶
class A
{
public virtual void M() => Console.Write("A");
}
class B : A
{
public new virtual void M() => Console.Write("B");
}
class C : B
{
public override void M() => Console.Write("C");
}
A a = new C();
B b = new C();
a.M();
b.M();
- A.
AB - B.
BB - C.
AC,B 用 new 开了新虚槽,A 的虚槽仍指向 A.M,C 覆盖的是 B 的虚槽 - D.
BC
查看答案与解析
正确答案C
解析: B 用 new virtual 创建了一条独立虚链:A 的虚槽未被改写(输出 A),B 的新虚槽被 C 覆盖(输出 C)。
关键点: new virtual 会“分裂”虚槽,基类引用与派生类引用看到不同实现。
0164 以下代码能否编译?
难度: 进阶
class A
{
public virtual void M() { }
}
class B : A
{
public sealed override void M() { }
}
class C : B
{
public override void M() { }
}
- A. 编译失败,不能重写 sealed 成员(CS0239)
- B. 编译通过
- C. 编译通过,运行抛异常
- D. 编译通过,但
C.M不生效
查看答案与解析
正确答案A
解析: sealed override 在 B 处终结虚链,C 再写 override 编译报 CS0239。
关键点: sealed 只作用于“覆盖点”,它本身就是 override。
0165 以下代码能否编译?
难度: 实战
sealed class A { }
class B : A { }
- A. 编译通过
- B. 运行抛异常
- C. 编译失败,不能从密封类型派生(CS0509)
- D. 编译通过,但 B 不继承 A
查看答案与解析
正确答案C
解析: sealed 类禁止被继承,任何派生声明都是编译错误 CS0509。
关键点: sealed 类没有派生类,这是设计契约而非运行时行为。
0166 以下代码输出什么?
难度: 进阶
class A
{
public A() => Console.Write(M() ?? "null");
public virtual string? M() => "A";
}
class B : A
{
private string? _s;
public B() => _s = "B";
public override string? M() => _s;
}
new B();
- A.
A - B.
B - C. 编译失败
- D.
null,基类构造函数中调用虚方法会派发到 B.M(),但 B 的构造函数体尚未执行,_s仍为 null
查看答案与解析
正确答案D
解析: 构造顺序是 B 的字段初始化 → A 的构造函数 → B 的构造函数体。A 的构造函数里调用虚方法 M() 会派发到 B.M,此时 B 的构造函数体尚未运行,_s 为 null,输出 null。
关键点: 构造函数里调用虚方法会看到派生类“未初始化完成”的状态。
0167 以下代码输出什么?
难度: 实战
class A
{
public virtual void M() => Console.Write("A");
}
class B : A
{
public override void M()
{
base.M();
Console.Write("B");
}
}
new B().M();
- A.
AB - B.
A - C.
B - D.
BA
查看答案与解析
正确答案A
解析: base.M() 显式调用基类实现,是非虚调用,输出 A 后再输出 B。
关键点: base 调用跳过虚分派,直接执行基类版本。
0168 以下代码输出什么?
难度: 进阶
class Animal { }
class Dog : Animal { }
class Shelter
{
public virtual Animal Get() => new Animal();
}
class DogShelter : Shelter
{
public override Dog Get() => new Dog();
}
Console.WriteLine(new DogShelter().Get().GetType().Name);
- A.
Animal - B.
Shelter - C. 编译失败
- D.
Dog,协变返回允许用更派生的引用类型覆盖
查看答案与解析
正确答案D
解析: C# 9 起支持协变返回:覆盖方法可以返回基方法返回类型的派生类型,DogShelter.Get 返回 Dog。
关键点: 协变返回只在“返回更派生的引用类型”时合法。
0169 以下代码能否编译?
难度: 进阶
class Animal { }
class Shelter
{
public virtual Animal Get() => new Animal();
}
class CatShelter : Shelter
{
public override string Get() => "x";
}
- A. 编译失败,返回类型必须是
Animal或其派生类型(CS0508) - B. 编译通过,任意类型都可覆盖
- C. 编译通过,运行抛异常
- D. 编译通过,返回
string
查看答案与解析
正确答案A
解析: string 与 Animal 没有继承关系,协变返回不适用,覆盖签名必须一致,编译报 CS0508。
关键点: 协变返回要求返回类型是“更派生”的引用类型,无关类型不行。
0170 以下代码能否编译?
难度: 进阶
class A
{
public virtual object M() => new();
}
class B : A
{
public override int M() => 1;
}
- A. 编译通过
- B. 运行抛异常
- C. 编译失败,协变返回仅适用于引用类型,
int不是object的引用派生类型(CS0508) - D. 编译通过,输出
1
查看答案与解析
正确答案C
解析: 虽然 int 能装箱成 object,但协变返回要求的是引用类型层面的继承关系,值类型不满足,报 CS0508。
关键点: 装箱转换不等于引用派生关系,协变返回只看类型层次。
0171 以下代码输出什么?
难度: 进阶
class A
{
public virtual void M() => Console.Write("A");
}
class B : A
{
public override void M() => Console.Write("B");
}
class C : B
{
public new void M() => Console.Write("C");
}
A a = new C();
a.M();
- A.
A - B.
C - C.
ABC - D.
B,C 用 new 隐藏的是 B 的虚实现,从 A 视角分派仍落到 B
查看答案与解析
正确答案D
解析: C 的 new 只隐藏了“通过 C/B 静态类型可见”的 B.M,而 A 的虚槽仍指向 B.M(A 虚链上 B 是最后一个 override),经 A 引用调用输出 B。
关键点: new 隐藏不影响基类虚槽的既有分派目标。
0172 以下代码输出什么?
难度: 实战
class A
{
public int X => 1;
}
class B : A
{
public new int X => 2;
}
A a = new B();
Console.WriteLine(a.X);
- A.
2 - B.
1 - C. 编译失败
- D. 运行抛异常
查看答案与解析
正确答案B
解析: 属性与字段一样可被 new 隐藏;通过 A 静态类型访问选中 A.X,输出 1。
关键点: new 隐藏同样适用于属性,静态类型决定取哪个成员。
0173 以下代码能否编译?
难度: 实战
abstract class A
{
public abstract void M();
}
class B : A { }
- A. 编译通过
- B. 运行抛异常
- C. 编译失败,
B未实现抽象成员M(CS0534) - D. 编译通过,但
B不能实例化
查看答案与解析
正确答案C
解析: 非抽象派生类必须实现所有继承的抽象成员;B 未实现 M,编译报 CS0534。
关键点: 抽象成员是强制契约,首次出现的非抽象派生类必须兑现。
0174 以下代码输出什么?
难度: 实战
abstract class A
{
public virtual void M() => Console.Write("A");
}
class B : A { }
new B().M();
- A. 编译失败,抽象类的成员必须 abstract
- B. 运行抛异常
- C. 输出空
- D.
A,抽象类可以提供 virtual 默认实现,派生类可继承
查看答案与解析
正确答案D
解析: 抽象类可以有非抽象成员;virtual 默认实现允许派生类直接继承,B 无需重写即可调用,输出 A。
关键点: abstract 修饰的是类型或成员本身,不排斥 virtual 实现。
0175 以下代码输出什么?
难度: 实战
class A
{
public virtual void M() => Console.Write("A");
}
class B : A
{
public override void M() => Console.Write("B");
}
class C : B
{
public override void M()
{
base.M();
Console.Write("C");
}
}
new C().M();
- A.
ABC - B.
BC - C.
AC - D.
C
查看答案与解析
正确答案B
解析: C.M 里的 base.M() 是静态调用,落到 B.M 输出 B,随后输出 C。
关键点: 多层 override 中 base 只上跳一层,不是一路跳到最基类。
0176 以下代码能否编译?
难度: 实战
class A
{
public void M() { }
}
class B : A
{
public override void M() { }
}
- A. 编译失败,基成员未标记 virtual/abstract/override(CS0506)
- B. 编译通过
- C. 编译通过,运行抛异常
- D. 编译通过,但隐藏基成员
查看答案与解析
正确答案A
解析: override 要求基类存在可覆盖的 virtual/abstract/override 成员;A.M 是普通方法,编译报 CS0506。
关键点: 想覆盖先声明 virtual,否则只能 new 隐藏。
0177 以下代码输出什么?
难度: 进阶
class A
{
public virtual void M() => Console.Write("A");
}
class B : A
{
public override void M()
{
base.M();
Console.Write("B");
}
}
class C : B
{
public override void M()
{
base.M();
Console.Write("C");
}
}
new C().M();
- A.
BC - B.
AC - C.
ABC - D.
AB
查看答案与解析
正确答案C
解析: C.M 调 base.M()(即 B.M),B.M 又调 base.M()(即 A.M),逐层输出 A、B、C。
关键点: 每层 override 里的 base 都指向其直接基类实现,形成链式执行。
0178 关于 override 与 new 的分派差异,哪项正确?
难度: 实战
- A.
override参与动态分派(由实际类型决定);new只在编译期按静态类型隐藏成员,通过基类引用调用不会到达 new 成员 - B.
override与new都是运行时动态分派 - C.
new只能在非 virtual 成员上使用 - D.
override要求基成员必须是new
查看答案与解析
正确答案A
解析: 二者的本质区别是分派时机:override 改写虚槽、运行时生效;new 建立同名新成员、编译期按静态类型选择。new 同样可用于隐藏 virtual 成员,且 override 与 new 互不要求。
关键点: 判断输出先看“静态类型 vs 实际类型”,再看成员是 override 还是 new。
0179 关于 sealed override,哪项正确?
难度: 实战
- A.
sealed override会取消 override 语义,变成静态调用 - B.
sealed override终止虚链,防止更下层派生类继续覆盖,但它本身仍是 override - C.
sealed只能用于类,不能用于方法 - D.
sealed override与new等价
查看答案与解析
正确答案B
解析: sealed override 既改写虚槽(仍是多态分派目标),又禁止再被下层覆盖;sealed 可以修饰类、方法、属性等,与 new 无关。
关键点: sealed override = “仍是 override,但这是虚链终点”。
0180 关于协变返回,哪项正确?
难度: 进阶
- A. 协变返回允许改变参数列表
- B. 协变返回允许任意改变返回类型
- C. 协变返回只对值类型有效
- D. 覆盖方法可在语言允许时返回比基方法返回类型更派生的引用类型,参数必须与基签名一致;C# 9 起支持
查看答案与解析
正确答案D
解析: 协变返回只放宽“返回类型”这一维,且必须是更派生的引用类型;参数类型必须与基签名完全一致(C# 不支持参数逆变覆盖)。
关键点: 协变是返回方向的单向放宽,签名其余部分不变。