提起子类、基类和方法继承这些概念,肯定大家都非常熟悉。毕竟,作为一门支持OOP的语言,掌握子类、基类是学习C#的基础。不过,这些概念虽然简单,但是也有一些初学者可能会遇到的坑,我们一起看看吧。

 

子类继承基类非私有方法

首先我们看最简单的一种,子类继承自基类,但子类对继承的方法没有任何改动

class Person
{
public void Greeting()
{
Console.WriteLine("Hello, I am Person");
}
} class Employee : Person
{ } class Program
{
static void Main(string[] args)
{
Person p = new Employee();
p.Greeting();
}
}

在这个例子中,作为子类的Employee自动继承了基类的Greeting方法,当在子类实例调用这个方法的时候,实际上调用的是基类的方法。这个例子非常简单,毋庸多言。

 

子类覆盖基类方法

接着是最常见的情况,子类覆盖基类的方法,典型的例子如下

class Person
{
public virtual void Greeting()
{
Console.WriteLine("Hello, I am Person");
}
} class Employee : Person
{
public override void Greeting()
{
Console.WriteLine("Hello, I am Employee");
}
} class Program
{
static void Main(string[] args)
{
Employee e = new Employee();
Person p = e;
p.Greeting();
e.Greeting();
}
}

同样,这段代码也很简单,基类方法通过关键字virtual表明方法可以被覆盖,子类通过关键字override实现对基类方法的覆盖,最后看调用部分,无论变量类型是子类还是基类,只要对象实际类型是子类,调用的方法都是子类覆盖的方法,这也是多态的实现基础。

 

子类隐藏基类方法

上面两个例子都非常简单,逻辑也很清楚,有点绕的要算子类隐藏基类方法的情况。

子类隐藏基类的非虚方法

基类被子类继承的方法可能是虚方法,也可能是非虚方法,先看非虚方法被子类隐藏的情况,隐藏基类方法使用的关键字是new

class Person
{
public void Greeting()
{
Console.WriteLine("Hello, I am Person");
}
} class Employee : Person
{
public new void Greeting()
{
Console.WriteLine("Hello, I am Employee");
}
} class Program
{
static void Main(string[] args)
{
Employee e = new Employee();
Person p = e;
p.Greeting();
e.Greeting();
}
}

这里的结果可能就出乎某些初学者的意料了,为什么明明是子类Employee的实例,却在不同的引用变量类型下呈现出了不一样的效果?为什么会调用到了基类里面的方法?

其实这跟C#的函数调用机制有关,一般来说,C#编译成MSIL之后,有两种函数调用方式。

  • Call 以非虚的方式调用方法,一般用于静态函数调用,因为静态函数不可能是虚的,但也可以以非虚的方式调用一个虚方法
  • Callvirt 以虚方式调用,一般用于非静态方法和虚方法的调用。如果调用的方法非虚,则引用变量类型决定了最终调用的方法;反之,如果调用的方法为虚,则实例变量类型决定最终调用的方法——因为可能出现方法重写,即,多态

用ILDASM打开我们的程序集看看,

证明了这里确实是用的Callvirt,而这个方法是非虚的方法,所以在两次调用中,引用变量类型Person和Employee就能够决定所调用的方法。两个类分别实现了自己的Greeting方法,没有出现子类覆盖基类方法的情况。这就解释了为什么两次调用结果不同。最后让我们来看看最复杂的一种情况

 

子类隐藏基类的虚方法

考虑下面的代码

class Person
{
public virtual void Greeting()
{
Console.WriteLine("Hello, I am Person");
}
} class Employee : Person
{
public new virtual void Greeting()
{
Console.WriteLine("Hello, I am Employee");
}
} class Manager : Employee
{
public override void Greeting()
{
Console.WriteLine("Hello, I am Manager");
}
} class Program
{
static void Main(string[] args)
{
Manager m = new Manager();
Person p = m;
Employee e = m;
p.Greeting();
e.Greeting();
m.Greeting();
}
}

猜一下输出应该是什么?这也是老胡曾经遇到过的一道笔试题,表面看着简单,但是不注意也会掉坑里

1,2,3,答案揭晓

是不是有点出乎意料呢,让我们来分析一下

首先,三次调用均是callvirt,而且方法Greeting是虚方法,我们需要考虑对象实例以决定要调用的方法。

  • 在第一次调用中,引用变量类型是Person,虽然对象实例类型Manger重写了Greeting方法,但是它重写的是继承自Manger基类Emplyee的Greeting方法,Person中Greeting方法在子类Manger中仅仅是被隐藏而没有被重写,所以这里调用的是Person中的Greeting
  • 而第二次调用中,引用变量类型是Employee,Employee的Greeting方法被Manager重写,所以这次调用到的是Manager中的Greeting
  • 最后一次调用毋庸多言,简单的重写案例而已

怎么样,是不是有小伙伴猜错结果了?

 

总结

在子类对基类有方法继承、重写和隐藏的情况下,有时候判断具体哪个方法被调用会有难度,但请记住以下要点:

  • 如果被调用方法非虚,那么只用关注引用变量类型就好,引用变量类型能决定调用方法在哪里
  • 如果调用方法为虚,我们需要站在引用变量类型的角度,审视该方法是否被对象类型所重写;若是,则调用对象类型的重写方法;反之,则再次让引用变量类型决定调用方法。

这样,当我们再遇到子类隐藏基类虚方法的情况,应用以上要点就可以拨云见日。

C#中子类对基类方法的继承、重写和隐藏的更多相关文章

  1. java中子类与基类变量间的赋值

    Java中子类与基类变量间的赋值 子类对象可以直接赋给基类变量. 基类对象要赋给子类对象变量,必须执行类型转换, 其语法是: 子类对象变量=(子类名)基类对象名; 也不能乱转换.如果类型转换失败Jav ...

  2. c++子类调用基类方法的一个例子

        Base.h #pragma once   class Base { public:     Base(void);     ~Base(void);     bool CreatClone( ...

  3. lua中基类和“继承机制”

    基类:基类定义了所有对于派生类来说普通的属性和方法,派生类从基类继承所需的属性和方法,且在派生类中增加新的属性和方法. 继承:继承是C++语言的一种重要机制,它允许在已定义的类的基础上产生新类. lu ...

  4. JAVA中子类会不会继承父类的构造方法

    声明:刚刚接触java不久,如果理解有错误或偏差望各位大佬强势批判 java中子类能继承父类的构造方法吗? 父类代码: class Father { String name ; //就不set/get ...

  5. 转:Java中子类是否可以继承父类的static变量和方法而呈现多态特性

    原文地址:Java中子类是否可以继承父类的static变量和方法而呈现多态特性 静态方法 通常,在一个类中定义一个方法为static,那就是说,无需本类的对象即可调用此方法,关于static方法,声明 ...

  6. Java中子类是否可以继承父类的static变量和方法而呈现多态特性

    静态方法 通常,在一个类中定义一个方法为static,那就是说,无需本类的对象即可调用此方法,关于static方法,声明为static的方法有以下几条限制: 它们仅能调用其他的static 方法. 它 ...

  7. java中子类继承父类程序执行顺序

    java中子类继承父类程序执行顺序 FatherTest.java public class FatherTest { private String name; public FatherTest() ...

  8. 关于在C#中对类中的隐藏基类方法和重写方法的理解

    最近在学习C#,在C#中的类看到重写和隐藏基类的方法这些概念.才开始感觉自己不是很理解这些概念.也区分不开这些概念.通过自己的查找资料和练习后.慢慢的理解了类中的隐藏和重写这个概念.在C#中只有在基类 ...

  9. C++中public,protected,private派生类继承问题和访问权限问题

    C++中public,protected,private派生类继承问题和访问权限问题 当一个子类从父类继承时,父类的所有成员成为子类的成员,此时对父类成员的访问状态由继承时使用的继承限定符决定. 1. ...

随机推荐

  1. 计算机网络之DNS常见攻击

    DNS欺骗 在Internet上存在的DNS服务器有绝大多数都是用bind来架设的,使用的bind版本主要为bind 4.9.5+P1以前版本和bind 8.2.2-P5以前版本.这些bind有个共同 ...

  2. python黑帽子之tcp客户端

    在渗透测试过程中,我们经常会需要创建一个tcp客户端来连接服务,发送垃圾数据,进行模糊测试等任务,所以我们来用python创建一个简单的tcp客户端. import sockettarget_host ...

  3. python R语言 入门常见指令

    环境是windows R语言安装包 install.packages("magrittr")

  4. 全网最深分析SpringBoot MVC自动配置失效的原因

    前言 本来没有计划这一篇文章的,只是在看完SpringBoot核心原理后,突然想到之前开发中遇到的MVC自动失效的问题,虽然网上有很多文章以及官方文档都说明了原因,但还是想亲自看一看,本以为很简单的事 ...

  5. openstack cinder-backup流程与源码分析

    在现在的云计算大数据环境下,备份容灾已经变成了一个炙手可热的话题,今天,和大家一起分享一下openstack是怎么做灾备的. [首先介绍快照] snapshot可以为volume创建快照,快照中保存了 ...

  6. Jmeter系列(37)- 详解 ForEach控制器

    如果你想从头学习Jmeter,可以看看这个系列的文章哦 https://www.cnblogs.com/poloyy/category/1746599.html 前言 ForEach 控制器一般和用户 ...

  7. spring和springmvc包扫描问题

    写这篇博客之前,橘子松必须感慨下!!找了我一下午加一晚上(md),问了几个朋友也没找到.凉了啊 在搭建ssm之前,我把controller service mapper包扫描用基本包扫描   都写在a ...

  8. 如何配置webpack让浏览器自动补全前缀

    一.postcss-loader有什么用? PostCSS 本身是一个功能比较单一的工具.它提供了一种方式用 JavaScript 代码来处理 CSS.它负责把 CSS 代码解析成抽象语法树结构(Ab ...

  9. 网页不让用户复制方法总汇,设置html禁止选择,保护源码,js禁止复制文字

    这篇文章主要讲解:右键复制失效方法.菜单"文件"-"另存为"失效方法.防止查看源代码进行复制的方法.防止页面缓存的方法.来达到一定的代码保护效果 右键复制失效方 ...

  10. Jmeter(十五) - 从入门到精通 - JMeter导入自定义的Jar包(详解教程)

    1.简介 原计划这一篇是介绍前置处理器的基础知识的,结果由于许多小伙伴或者童鞋们在微信和博客园的短消息中留言问如何引入自己定义的Jar包呢???我一一回复告诉他们和引入插件的Jar包一样的道理,一通百 ...