C#中实现多继承的方法

近日看到了一个贴子,就是在C#语言中,如何实现多继承的问题。相信涉猎c#不多的人(像我这样的菜鸟),一看就觉得很可笑,c#肯定是不能实现多继承的啊。都知道在c++中因为实现多继承会有很多的歧义问题,所以在c#中就把多继承给取消了,而用接口来实现!但是想想,如果是初学者肯定不会不会问这样的问题。肯定是个高手,然后就开始上网查资料!然后发现真的可以实现!

说起多继承,首先大家可以想想这个问题:你知道在C#中怎么实现多继承吗?

主流的答案无非2种。

答案一:用接口啊,一个类可以继承自多个接口的。
答案二:C#不支持多继承,C++才支持多继承,多继承会让代码变得很乱,因此微软在设计C#的时候放弃了多继承。

能够知道答案二的人显然懂的更多,我也在很长一段时间内相信C#不支持多继承,直到2013年5月的一个项目中,我偶然的发现自己的代码就完全实现了真正意义的多继承。

先说说什么是真正意义的多继承。真正的多继承应该是像C++那样的,而不是说像在C#里面一个类继承了多个接口就叫多继承。在C#中,如果一个类实现了多个接口,那么要为每个接口写实现,如果接口被多个类继承,那么就会有重复的代码,这显然是无法接受的。

然而C++那样的多继承也确确实实给编码带来了很大的麻烦,我也相信微软真的是因为意识到了多继承的不合理之处才在C#中摈弃了这个特性。而我在C#中实现的多继承,第一是真正的多继承,第二代码写的很合理。

请看案例

假如你有一个类叫老虎,还有一个类叫苍蝇。现在你想新创一个超级老虎类,一种可以飞的老虎。在C++中,你可以定义一种超级老虎类,让其继承自老虎和苍蝇,这样这种老虎就可以飞了。然而,问题出现了,这种超级老虎由于同时也继承自苍蝇,而苍蝇下面有个方法叫吃,参数类型是屎。吃屎的这个方法显然跟我们的超级老虎太不搭了。

虽然这个例子有些夸张,但是很多C++程序员真的就是这样在设计代码。由于子类继承了多个父类,而多个父类肯定有些成员跟这个子类不搭调,于是子类的调用者就很难受了。比如上面这个例子,当调用者拿到超级老虎的一个实例时,发现超级老虎下面怎么会有个吃屎的方法呢!!!真的是要笑死人了。

C++要这样允许多继承就必然会造成这个问题。C#程序员就绝对不会写出这样滑稽的代码。对于C#程序员,肯定是要把这个飞的方法提成接口的,然后让苍蝇类和超级老虎类都继承自这个接口。这样,苍蝇会飞,超级老虎也会飞。是不是完美解决这个问题?

问题看上去解决了,但是,假如我跟你说苍蝇飞的方法跟超级老虎飞的方法需要一模一样:首先张开双翅,身体前倾,拍打双翅,起飞,继续拍打。我们肯定不能把同一份代码copy一份吧,那是属于入门级程序员干的事,我们现在已经没资格干那事了。那怎么办呢?简单快速的做法是使用静态方法,比如FlyHelper.Fly(...)。

静态方法解决了代码重用的问题,但写起来始终觉得哪里不对劲。我的超级老虎类和苍蝇都明明继承了飞了啊,为什么还要这样调用一句静态方法。如果以后哪天我想让我的猪也能飞起来,那岂不是还要来调用这个静态方法。

到底怎样才能在C#中实现像C++那样优雅的继承呢?

答案揭晓

答案其实很简单,那就是给IFly接口写扩展方法。

首先请看这个空接口的定义,及其扩展方法(注意泛型限制):

复制代码 代码如下:

namespace Interface
{
    //飞的接口
    public interface IFly
    {
    }
    //扩展方法
    public static class ExtendFly
    {
        public static void StartFly<T>(this T example) where T : IFly
        {
            Console.WriteLine("准备");
            Console.WriteLine("张开双翅");
            Console.WriteLine("起飞");
            Console.WriteLine("我飞,我飞,我飞飞飞");
        }
    }
}

再看老虎和苍蝇的实现:

复制代码 代码如下:

namespace Interface
{
    //苍蝇类实现飞的接口
    public class flies : IFly
    {
        public void fly()
        {
            //调用接口中飞的方法
            this.StartFly();
        }
    }
}
namespace Interface
{
    //老虎类
    public class Tiger
    {
        public void introduce()
        {
            Console.WriteLine("I am a tiger");
        }
    }
}

再看超级老虎的实现:

复制代码 代码如下:

namespace Interface
{
    //超级老虎类,继承了老虎类,并实现了飞的方法
    public class SuperTiger : Tiger, IFly
    {
        public override void introduce()
        {
            Console.WriteLine("大家好,我是超级老虎哦!");
        }

public void TigerFly()
        {
            //调用接口中飞的方法
            this.StartFly();
        }
    }
}

怎么样,你看明白了吗?这个实现是不是很简单呢?好处是不是大大的有呢?

当以后哪天老板让你实现一个会飞的超级猪的话,你只需要让你的超级猪继承“I飞”接口就行了。当哪天老板又不想要这个超级猪飞的话,你也只需要将这个接口继承删掉而已。如果你正在开发一个动物王国程序,你可以将飞的功能注入到任何一种动物身上。想想是不是都觉得很爽。

总结

最后,再让我们回顾一下之前用C++写的超级老虎吃屎的变态例子。这实际上不是C++的错,而是程序员用错了多继承。虽然在语法上C++没有限制程序员怎么去写多继承,但是从上面的例子分析来看,我们很容得出这样一个结论:

当需要写多继承的时候,被继承的父类只能是一个功能,而不应是一个完整的类。

如果按照这个思路,那么今天的这个例子在C++中就可以这样写,首先提一个Flyable的类出来,然后让超级老虎和苍蝇都继承这个Flyable。

在C#中,虽然实现多继承的代码稍微绕了个弯,但是多继承带来的好处是非常明显的:对不同的类实现注入式的功能,让你的代码更符合面向对象的思想。

时间: 2014-08-28

C#基础继承和多态详解

继承 在现有类(称为基类.父类)上建立新类(称为派生类.子类)的处理过程为继承.派生类能自动获取基类(除了构造函数和析构函数外的所有成员),可以在派生类中添加新的属性和方法扩展其功能. 复制代码 代码如下: using System;using System.Collections.Generic;using System.Linq;using System.Web; public class Person{ private string _id;    public string id   

c#继承与多态使用示例

继承和多态 派生类具有基类所有非私有数据和行为以及新类自己定义的所有其他数据或行为,即子类具有两个有效类型:子类的类型和它继承的基类的类型. 对象可以表示多个类型的能力称为多态性. 多态性示例 复制代码 代码如下: public class Parent    {        public Parent() { }        public void MethodA()        {            Console.WriteLine("调用MethodA()");   

C# 面向对象三大特性:封装、继承、多态

面向对象有封装.继承.多态这三个特性,面向对象编程按照现实世界的特点来管理复杂的事物,把它们抽象为对象,具有自己的状态和行为,通过对消息的反应来完成任务.这种编程方法提供了非常强大的多样性,大大增加了代码的重用机会,增加了程序开发的速度,将具备独立性特制的程序代码包装起来,修改部分程序代码时不至于会影响到程序的其他部分. 1.封装 每个对象都包含它进行操作所需要的所有信息,封装只公开代码单元的对外接口,而隐藏其具体实现,尽量不对外公开代码.使用封装有很多好处,从设计角度来讲,封装可以对外屏蔽一些

C# 泛型的简单理解(安全、集合、方法、约束、继承)分享

前言 泛型允许你在编译时实现类型安全.它们允许你创建一个数据结构而不限于一特定的数据类型.然而,当使用该数据结构时,编译器保证它使用的类型与类型安全是相一致的.泛型提供了类型安全,但是没有造成任何性能损失和代码臃肿.在这方面,它们很类似于C++中的模板,不过它们在实现上是很不同的. 使用泛型集合 .NET 2.0的System.Collections.Generics 命名空间包含了泛型集合定义.各种不同的集合/容器类都被"参数化"了.为使用它们,只需简单地指定参数化的类型即可. 复制

c#继承中的函数调用实例

本文实例讲述了c#继承中的函数调用方法,分享给大家供大家参考.具体分析如下: 首先看下面的代码: 复制代码 代码如下: using System;   namespace Test {     public class Base     {         public void Print()         {             Console.WriteLine(Operate(8, 4));         }           protected virtual int Ope

C#中事件的继承实例分析

通常来说,C#中的子类无法调用父类的事件,但是可以通过在父类中创建一个方法来调用父类的事件,而子类通过调用父类的方法来触发事件. 具体实现代码如下: class parent { protected string name; public event Handle OnEvent; protected SendEvent(HandleArgs args) { if (OnEvent != null) { OnEvent(this, args); } } } class clild : paren

C#中面向对象编程机制之继承学习笔记

继承反应了类和类之间的关系. 世界上很多事物都是有共性的,共性的那一部分我们就抽象为基类,用于派生其它类,这样提高了代码的复用性,使得代码的结构清晰易读,而且易于代码的扩展和维护. C#的继承只能继承自一个基类,这一点不同于C++的继承. C#的继承具有传递性,即B继承自A,C继承自B,则C具有A的所有特性. C#的继承隐式为public的. 假如不在派生类构造器中显示调用一个基类构造器,编译器会自动插入对基类的默认构造器的一个调用,然后才会执行派生类构造器中的代码, 如果基类没有默认的构造器,

详解C#面相对象编程中的继承特性

继承(加上封装和多态性)是面向对象的编程的三个主要特性(也称为"支柱")之一. 继承用于创建可重用.扩展和修改在其他类中定义的行为的新类.其成员被继承的类称为"基类",继承这些成员的类称为"派生类".派生类只能有一个直接基类.但是,继承是可传递的.如果 ClassB 派生出 ClassC,ClassA 派生出 ClassB,则 ClassC 会继承 ClassB 和 ClassA 中声明的成员. 注意 结构不支持继承,但可以实现接口. 从概念上来

浅谈C# 类的继承

继承 一个类可以继承自另一个类.在 C#中,类与类之间只存在单一继承.也就是说,一个类的直接基类只能有一个.当类与类之间实现继承的时候,子类可以将它的直接基类的所有成员当做自己的成员,除了类的静态构造方法.实例构造方法和析构方法.但是,虽然基类的所有成员都可以当做子类的成员,但是如果基类的成员设置了不同的访问权限,则派生类可以访问的成员也随之不同.C#的继承是可以传递的,如果类C从类B派生,而类B从类A派生,则类C将继类B的所有成员,也继承类A的所有成员(各个基类的静态构造方法.实例构造方法和析

C# Page用于各页面继承功能实例

本文实例讲述了C# Page用于各页面继承功能的实现方法.分享给大家供大家参考.具体方法如下: IBasePage.cs文件如下: 复制代码 代码如下: /// <summary> /// 用于页面或用户控件 /// </summary> public interface IBasePage {         /// <summary>         /// 数据缓存类         /// </summary>         DbCache Cac

js实现页面打印功能实例代码(附去页眉页脚功能代码)

复制代码 代码如下: <html> <head></head> <style type="text/css" media="screen"> @media print{ .print {display:block;} .notPrint {display:none;} } </style> <script language="javascript"> function pre

C# PDF Page操作设置页面切换按钮的方法

概述 在以下示例中,将介绍在PDF文档页面设置页面切换按钮的方法.示例中将页面切换按钮的添加分为了两种情况,一种是设置按钮跳转到首页.下页.上页或者最后一页,另一种是设置按钮跳转到指定页面.两种方法适应不同的程序设计需要,可自行选择合适的添加方法. 说明 这里的代码示例需要使用类库Spire.PDF for .NET,版本4.0 .在使用该类库时,在项目程序中引用Spire.Pdf.dll即可(dll文件在安装路径下的Bin文件中获取). 如: 代码操作示例(供参考) 1.跳转至特定页(首页.下

SpringBoot创建JSP登录页面功能实例代码

添加JSP配置 1.pom.xml添加jsp解析引擎 <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>javax.s

js禁止浏览器页面后退功能的实例(推荐)

实例如下所示: <script> $(function(){ if(window.location.href.indexOf("/login") > -1) { //防止页面后退 history.pushState(null, null, document.URL); window.addEventListener('popstate', function () { history.pushState(null, null, document.URL); }); }

ASP.NET jQuery 实例11 通过使用jQuery validation插件简单实现用户登录页面验证功能

简单来说,jQuery validation插件就是来校验表单form里面元素输入的内容是否满足业务规则,如果不满足,可以给出用户自定义的提示信息.该插件不仅默认有一些校验规则,如校验内容是否为空,内容的长度是否符合给定的值,还可以根据用户自定义业务规则,而且错误提示信息,也可以根据用户的要求自定义显示.看来这个插件的功能确实很强大,是不是迫不及待想使用了.好的,那我们就开始简介如何使用它. jQuery validation 插件下载地址:http://plugins.jquery.com/p

JavaWeb实现用户登录注册功能实例代码(基于Servlet+JSP+JavaBean模式)

下面通过通过图文并茂的方式给大家介绍JavaWeb实现用户登录注册功能实例代码,一起看看吧. 一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBean模式(MVC)适合开发复杂的web应用,在这种模式下,servlet负责处理用户请求,jsp负责数据显示,javabean负责封装数据. Servlet+JSP+JavaBean模式程序各个模块之间层次清晰,web开发推荐采用此种模式. 这里以一个最常用的用户登录注册程序来讲解Servlet+JS

Android 百度地图POI搜索功能实例代码

在没介绍正文之前先给大家说下poi是什么意思. 由于工作的关系,经常在文件中会看到POI这三个字母的缩写,但是一直对POI的概念和含义没有很详细的去研究其背后代表的意思.今天下班之前,又看到了POI这三个字母,决定认认真真的搜索一些POI具体的含义. POI是英文的缩写,原来的单词是point of interest, 直译成中文就是兴趣点的意思.兴趣点这个词最早来自于导航地图厂商.地图厂商为了提供尽可能多的位置信息,花费了很大的精力去寻找诸如加油站,餐馆,酒店,景点等目的地,这些目的地其实都可

Angular+Bootstrap+Spring Boot实现分页功能实例代码

需要用到的js angular.js(用angular.min.js会导致分页控件不显示) ui-bootstrap-tpls.min.js angular-animate.js 需要用到的css bootstrap.min.css 由于本项目使用了路由,所以讲js以及css文件的应用都放在一个主html,请同学们在html页面中添加以上文件 在开始之前,我先简单介绍下分页的原理. 分页的实质其实就是一条sql语句, 比如查找第二页,即第16到第30条数据 在MySQL中是select * fr

mui上拉加载功能实例详解

最近在做移动端的项目,用到了mui的上拉加载,整理如下: 1.需要引入的css.js <link rel="stylesheet" href="common/mui/css/mui.min.css" rel="external nofollow" > <script src="js/jquery-3.2.0.min.js"></script> <script src="com