C#子类对基类方法的继承、重写与隐藏详解

前言

提起子类、基类和方法继承这些概念,肯定大家都非常熟悉。毕竟,作为一门支持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#子类对基类方法的继承、重写与隐藏的文章就介绍到这了,更多相关C#子类对基类方法继承、重写与隐藏内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C#实现可捕获几乎所有键盘鼠标事件的钩子类完整实例

    本文实例讲述了C#实现可捕获几乎所有键盘鼠标事件的钩子类.分享给大家供大家参考,具体如下: using System; using System.Text; using System.Runtime.InteropServices; using System.Reflection; using System.Windows.Forms; namespace MouseKeyboardLibrary { /// <summary> /// Abstract base class for Mous

  • C#中派生类调用基类构造函数用法分析

    本文实例讲述了C#中派生类调用基类构造函数用法.分享给大家供大家参考.具体分析如下: 这里的默认构造函数是指在没有编写构造函数的情况下系统默认的无参构造函数 1.当基类中没有自己编写构造函数时,派生类默认的调用基类的默认构造函数 例如: public class MyBaseClass { } public class MyDerivedClass : MyBaseClass { public MyDerivedClass() { Console.WriteLine("我是子类无参构造函数&qu

  • 不能在子类或外部类发布C#事件代码分析

    复制代码 代码如下: using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks; namespace EventStudy{    class Program    {        static void Main(string[] args)        {        }    } class Base    {      

  • C++/JAVA/C#子类调用父类函数情况总结

    时间久了就容易记不清了,特留存备用查看 c++ 1.构造函数调用   常用初始化列表  或者显示调用 1.1同一个类中构造函数调用构造函数   尽量不要这样做,因为结果不确定!避免麻烦 可以把共用的代码封装成一个私有的成员函数,然后在构造函数内统一调用. 1.2子类构造函数调用基类构造函数 -----基类有默认构造函数时,可以在子类不写,则隐式调用 -----基类无/有默认构造函数时,在子类构造函数初始化列表处调用,则显示调用     基类类名(参数) class Base { public:

  • C#中子类调用父类的实现方法

    本文实例讲述了C#中实现子类调用父类的方法,分享给大家供大家参考之用.具体方法如下: 一.通过子类无参构造函数创建子类实例 创建父类Person和子类Student. public class Person { public Person() { Console.WriteLine("我是人"); } } public class Student : Person { public Student() { Console.WriteLine("我是学生"); } }

  • .NET/C#如何判断某个类是否是泛型类型或泛型接口的子类型详解

    前言 泛型:通过参数化类型来实现在同一份代码上操作多种数据类型.利用"参数化类型"将类型抽象化,从而实现灵活的复用.在.NET类库中处处都可以看到泛型的身影,尤其是数组和集合中,泛型的存在也大大提高了程序员的开发效率.更重要的是,C#的泛型比C++的模板使用更加安全,并且通过避免装箱和拆箱操作来达到性能提升的目的.因此,我们很有必要掌握并善用这个强大的语言特性. C#泛型特点: 1.如果实例化泛型类型的参数相同,那么JIT编辑器会重复使用该类型,因此C#的动态泛型能力避免了C++静态模

  • C#子类对基类方法的继承、重写与隐藏详解

    前言 提起子类.基类和方法继承这些概念,肯定大家都非常熟悉.毕竟,作为一门支持OOP的语言,掌握子类.基类是学习C#的基础.不过,这些概念虽然简单,但是也有一些初学者可能会遇到的坑,我们一起看看吧. 子类继承基类非私有方法 首先我们看最简单的一种,子类继承自基类,但子类对继承的方法没有任何改动 class Person { public void Greeting() { Console.WriteLine("Hello, I am Person"); } } class Employ

  • Python类的继承和多态代码详解

    Python类的继承 在OOP(ObjectOrientedProgramming)程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类.父类或超类(Baseclass.Superclass). 我们先来定义一个classPerson,表示人,定义属性变量name及sex(姓名和性别): 定义一个方法print_title():当sex是male时,printman:当sex是female时,prin

  • C++/java 继承类的多态详解及实例代码

    C++/java 继承类的多态详解 学过C++和Java的人都知道,他们二者由于都可以进行面向对象编程,而面向对象编程的三大特性就是封装.继承.多态,所有今天我们就来简单了解一下C++和Java在多态这方面的不同. 首先我们各看一个案例. C++ //测试继承与多态 class Animal { public: char name[128]; char behavior[128]; void outPut() { cout << "Animal" << endl

  • C++ 类的继承与派生实例详解

     C++ 类的继承与派生实例详解 继承性是面向对象程序设计最重要的特性之一,使软件有了可重用性,C++提供的类的继承机制. 继承与派生的概念 一个新类从已有的类那里获得已有的特性,这种现象称为类的继承.同样也可以说成已有的类派生出来了新的类.类A继承自类B也就是类B派生了类A.所以继承和派生的关系就像小学时把字句和被字句的造句一样.有了继承与派生后,就有了父类/基类与子类/派生类,C++中将类B称为父类/基类,将类A称为子类/派生类. 派生类的声明: #include <iostream> u

  • java中重写equals()方法的同时要重写hashcode()方法(详解)

    object对象中的 public boolean equals(Object obj),对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true: 注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码.如下: (1) 当obj1.equals(obj2)为true时,obj1.hashCode() == obj2.hashCode()必须为true (2) 当obj

  • 关于javascript原型的修改与重写(覆盖)差别详解

    每个JavaScript函数都有prototype属性(javascript对象没有这个属性),这个属性引用了一个对象,这个对象就是原型对象.javascript允许我们修改这个原型对象. 修改有2种方式: 方式1:在原有的原型对象上增加属性或者方法 function Person() { } Person.prototype.add = function(){ alert(this.name); }; Person.prototype.name = "aty"; var p1 = n

  • SpringBoot中shiro过滤器的重写与配置详解

    目录 问题 解决方案 实现代码 1.重写shiro 登录 过滤器 2.重写role权限 过滤器 3.配置过滤器 问题 遇到问题:在前后端分离跨域访问的项目中shiro进行权限拦截失效 (即使有正确权限的访问也会被拦截) 时造成302重定向错误等问题报错:Response for preflight is invalid (redirect) 1.302原因:使用ajax访问后端项目时无法识别重定向操作 2.shiro拦截失效原因:跨域访问时有一种带预检访问的跨域,即访问时先发出一条methods

  • Java单例模式继承覆盖多态原理详解

    1.单例模式: 1)提出原因 是由gof 也就是四人组提出来的.为了保证jvm中某一类型的java对象永远只有一个,同时也是为了节省内存的开销.因为外面程序可以通过new的方法直接调用类里面的构造方法.导致该类的对象不止一个. 2)定义 单例模式的意思就是只有一个实例.单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.这个类称为单例类. A.构造方法私有化: B.对外提供一个公开的.静态的.获取当前类型对象的方法 C.提供一个当前类型的静态变量. 3)分类 A.饿汉式单例

  • C+继承之同名覆盖,函数重写与多态详解

    目录 同名覆盖 函数重写 多态 总结 如果父类成员和子类成员名字相同是否允许?会发生什么? 同名覆盖 #include<iostream> using namespace std; class Base { public: int m_data; Base():m_data(1)//父类初始化为1 { } }; class Derived : public Base { public: int m_data; Derived():m_data(2)//子类初始化为2 { } }; int ma

  • JAVA基础之继承(inheritance)详解

    继承(inheritance)是Java OOP中一个非常重要的概念.继承是在复用已存在的类的方法和域的基础上,还可以添加新的方法和域.Java用extends关键字来表示继承关系(is-a).被继承的类称为超类(superclass).基类(base class).父类(parent class),而新类被称为子类(subclass).派生类(derived class)或孩子类(child class). 1.class:编程语言中的基本单位.将数据和功能封装到了一起. 2.基类包含其所有导

随机推荐