C#接口与抽象类学习笔记

本笔记摘抄自:https://www.cnblogs.com/solan/archive/2012/08/01/CSharp06.html,记录一下学习过程以备后续查用。

摘要:

抽象类:是一种特殊的类,可以定义具有实现的方法,也可以定义未实现的方法契约,本身不能被实例化,只能在派生类中进行实例化。接口:对一

组方法签名进行统一的命名,只能定义未实现的方法契约,本身也不能被实例化,只能在实现类中进行实例化。

二者都可以有部分数据成员(如:属性),它们貌似有着相同的“契约”功能,但对各自的派生类(实现类)又有着不同的要求,那么,到底它们有何

异同呢?下面将从四个方面来讲解它们的相同与不同之处。

一、定义

抽象类 不能实例化。抽象类的用途是提供多个派生类可共享的基类的公共定义,是对类进行抽象,可以有实现,也可以不实现。使用关键字abstract

进行定义。

下面定义一个抽象类:

public abstract class Code_06_03
{
}

通过ISDASM来看一下生成的IL:

.class abstract auto ansi nested public beforefieldinit Code_06_03
       extends [mscorlib]System.Object
{
} // end of class Code_06_03

可以看以,抽象类实际上是继承了System.Object类,并且编译器为它生成了一个默认的构造函数。

接口 它是对一组方法签名进行统一命名,是对一组行为规范的定义,使用关键字interface进行定义。

下面定义一个接口:

public interface ICode_06_01
{
}

通过ISDASM来看一下生成的IL:

.class interface abstract auto ansi nested public ICode_06_01
{
} // end of class ICode_06_01

可以看到,接口实际上是把它当成抽象类来看待,但是没有构造函数。无论是抽象类拥有构造函数,还是接口不拥有构造函数,它们都是不能被实例

化的。

二、成员的区别

抽象类 描述:

1)可以定义抽象方法,抽象方法没有具体实现,仅仅是一个方法的契约,在子类中重写该方法。抽象类可以重写父类的虚方法为抽象方法。

2)可以定义非抽象方法,但要求该方法要有具体实现,如果该方法是虚方法,则在子类中可以重写该方法。

3)可以定义字段、属性、抽象属性、事件及静态成员。

下面是对类Code_06_03的扩充:

class Program
    {
        /// <summary>
        /// 抽象类
        /// </summary>
        public abstract class Code_06_03
        {
            Dictionary<Guid, string> root = new Dictionary<Guid, string>();
            public string Sex { get; set; }
            public abstract string Address { get; }
            public abstract int Add(int a, int b);

            protected virtual string GetAddress(string addressID)
            {
                return addressID + " 广东";
            }

            public void AddRoot(Guid id, string rootName)
            {
                root.Add(id, rootName);
                OnAddRoot();
            }

            public event EventHandler AddRootEvent;

            void OnAddRoot()
            {
                AddRootEvent?.Invoke(this, null);
            }

            public string this[Guid key]
            {
                get
                {
                    return root[key];
                }
                set
                {
                    root[key] = value;
                }
            }
        }

        static void Main(string[] args)
        {

        }
    }

View Code

2.1抽象方法public abstract int Add(int a, int b);的IL:

.method public hidebysig newslot abstract virtual
        instance int32  Add(int32 a,
                            int32 b) cil managed
{
} // end of method Code_06_03::Add

编译器把Add方法当作一个虚方法,在子类中可以被重写。

2.2虚方法protected virtual string GetAddress(string addressID)的IL:

.method family hidebysig newslot virtual
        instance string  GetAddress(string addressID) cil managed
{
  // 略过
} // end of method Code_06_03::GetAddress

它本来就是一个虚方法,所以编译器并没有特殊对待它。

2.3方法public void AddRoot(Guid id, string rootName)的IL:

.method public hidebysig instance void  AddRoot(valuetype [mscorlib]System.Guid id,
                                                string rootName) cil managed
{
  // 略过
} // end of method Code_06_03::AddRoot

它也是一个普通的对象方法。

接口 描述:

1)可以定义属性及索引器,但不能定义字段。

2)可以定义事件。

3)可以定义方法,仅仅是方法签名的约定,不得有实现,在实现类中对该方法进行具体实现,有点类似于抽象类的抽象方法。

4)不可以定义虚方法。

5)不可以定义任何静态成员。

6)接口成员默认是全开放的,不得有访问修饰符。

下面是对类Code_06_01的扩充:

class Program
    {
        /// <summary>
        /// 接口
        /// </summary>
        public interface ICode_06_01
        {
            string Name { get; set; }
            int Add(int a, int b);
            event EventHandler AddEvent;
        }

        static void Main(string[] args)
        {

        }
    }

View Code

2.4方法int Add(int a, int b);的IL:

.method public hidebysig newslot abstract virtual
        instance int32  Add(int32 a,
                            int32 b) cil managed
{
} // end of method ICode_06_01::Add

可以看到,定义的时候,我们并没有为其指定可访问修饰符(编译器也不允许我们明文指定其可访问修饰符),但编译器默认将它的访问级

别指定为public,另外是把它当作一个抽象的虚方法。

至于成员属性和事件,编译器则将它们当作普通的对象属性和对象事件对待,会为它们生成相应的get/set和add/remove 方法,并无特别之

处。

三、实现方式的区别

抽象类 实现:

由于抽象类也是类,所以对它的实现就像普通的继承一样,子类通过继承可以得到抽象类的公有成员,且可以重写部分成员,如虚方法和抽象

方法等。

下面是对Code_06_03类的实现:

class Program
    {
        /// <summary>
        /// 抽象类
        /// </summary>
        public abstract class Code_06_03
        {
            Dictionary<Guid, string> root = new Dictionary<Guid, string>();
            public string Sex { get; set; }
            public abstract string Address { get; }

            /// <summary>
            /// 抽象方法ADD
            /// </summary>
            /// <param name="a"></param>
            /// <param name="b"></param>
            /// <returns></returns>
            public abstract int Add(int a, int b);

            /// <summary>
            /// 虚方法GetAddress
            /// </summary>
            /// <param name="addressID"></param>
            /// <returns></returns>
            protected virtual string GetAddress(string addressID)
            {
                return addressID + " 广东";
            }

            public void AddRoot(Guid id, string rootName)
            {
                root.Add(id, rootName);
                OnAddRoot();
            }

            public event EventHandler AddRootEvent;

            void OnAddRoot()
            {
                AddRootEvent?.Invoke(this, null);
            }

            public string this[Guid key]
            {
                get
                {
                    return root[key];
                }
                set
                {
                    root[key] = value;
                }
            }
        }

        /// <summary>
        /// 抽象类的实现
        /// </summary>
        public class Code_06_04 : Code_06_03
        {
            public override int Add(int a, int b)
            {
                return a + b;
            }
            protected override string GetAddress(string addressID)
            {
                return "GuangDong";
            }

            readonly string addressPrefix = "China ";
            public override string Address
            {
                get { return addressPrefix; }
            }
        }

        static void Main(string[] args)
        {

        }
    }

View Code

通过ISDASM来看一下生成的IL:

可以看到类Code_06_04是标准地对继承类Code_06_03,两个重写的方法Add和GetAddress都是普通的对象方法,只是依然被

当作虚方法来看待。

3.1方法Add的IL:

.method public hidebysig virtual instance int32
        Add(int32 a,
            int32 b) cil managed
{
  // 略过
} // end of method Code_06_04::Add

3.2方法GetAddress的IL:

.method family hidebysig virtual instance string
        GetAddress(string addressID) cil managed
{
  // 略过
} // end of method Code_06_04::GetAddress

因为这两个方法保持着虚方法的特性,所以对于Code_06_04类的子类,同样还可以重写这两个方法。属性成员Address这里还

是一普通的对象属性。

接口 实现

对接口的实现跟对抽象类的实现相似,下面是对接口ICode_06_01的实现:

class Program
    {
        /// <summary>
        /// 接口
        /// </summary>
        public interface ICode_06_01
        {
            string Name { get; set; }
            int Add(int a, int b);
            event EventHandler AddEvent;
        }

        /// <summary>
        /// 接口的实现
        /// </summary>
        public class Code_06_02 : ICode_06_01
        {
            public string Name { get; set; }

            public int Add(int a, int b)
            {
                OnAdded();
                return a + b;
            }

            public event EventHandler AddEvent;

            void OnAdded()
            {
                AddEvent?.Invoke(this, null);
            }
        }

        static void Main(string[] args)
        {

        }
    }

View Code

通过ISDASM来看一下生成的IL:

它与普通类的区别不大,只是很明确的是实现了接口ICode_06_01,来看一下它的IL:

.class auto ansi nested public beforefieldinit Code_06_02
       extends [mscorlib]System.Object
       implements LinkTo.Test.InterfaceAndAbstractClass.Program/ICode_06_01
{
} // end of class Code_06_02

可以看到,类Code_06_02不仅继承于System.Object类,同时还实现了接口ICode_06_01。再来看一下对于接口中的方法,编

译器是如何处理的?

3.3方法Add的IL:

.method public hidebysig newslot virtual final
        instance int32  Add(int32 a,
                            int32 b) cil managed
{
  // 略过
} // end of method Code_06_02::Add

编译器认为Add方法具有虚方法的特性。而对于属性和事件,依然是普通的实现,如get/set、add/remove。另外,接口还支持

显示实现接口,我们上面讨论的Code_06_02类对接口的实现默认是隐式实现。

在接口的实现类内部,可以存在一个与接口某一方法名(包括签名)完全相同的方法,但要求对接口实现的那个方法必须是显

示实现,如下代码:

public int Add(int a, int b)
        {
            return a + b;
        }

        int ICode_06_01.Add(int a, int b)
        {
            OnAdded();
            return a + b;
        }

可以看出显示实现就是在方法前加上接口名和点号(ICode_06_01.),另外方法是不能有可访问修饰符的,编译器会对其进行

private处理。那如何才能调用显示实现的接口方法呢?可以将实现类的对象转为一个接口变量,再调用该变量的相应方法,如下

代码:

static void Main(string[] args)
        {
            Code_06_02 code0602 = new Code_06_02();
            ICode_06_01 icode0602 = code0602;
            var result = icode0602.Add(1, 2);
            Console.WriteLine($"Result={result}");
            Console.Read();
        }

而对于抽象类的实现,是不能进行显示实现的。

四、应用中的区别

1)抽象类保留一普通类的部分特性,定义可能已经实现的方法行为,方法内可以对数据成员(如属性)进行操作,且方法可以

相互沟通。而接口仅仅是定义方法的签名,就像规则,只是约定,并没有实现。

2)抽象类的派生类可以原封不动地得到抽象类的部分成员,接口的实现类如果想要得到接口的数据成员,则必须对其进行重写。

3)一个类只能继承于一个类(含抽象类),但可以实现多个接口,并且可以在继承一个基类的基础上,同时实现多个接口。

4)抽象类和接口都不能对其使用密封sealed,事实上这两者都是为了被其他类继承和实现,对其使用sealed是没有任何意义的。

5)抽象类可以对接口进行实现。

6)抽象类更多的用于“复制对象副本”,就是我们常说的“子类与父类有着is a的关系”,它更多关注于一个对象的整体特性。接口

更多倾向于一系列的方法操作,这些操作在当前上下文中既有着相同作用对象,又相互隔离。

7)某些时候,抽象类可以与接口互换。

通过生活中常见的红娘搭线的示例:红娘(Matchmaker)安排相亲者(wooer)见面并指导场面话,来说明接口与抽象类给我们

带来的方便性。

下面代码演示不使用接口与抽象类的红娘搭线:

class Program
    {
        /// <summary>
        /// 红娘类
        /// </summary>
        public class Matchmaker
        {
            string message;

            /// <summary>
            /// 场面话、客套话指导
            /// </summary>
            public void Teach()
            {
                message = "曾经有一份真挚的爱情摆在我面前……";
                Wooer wooer = new Wooer();
                wooer.Say(message);
            }
        }

        /// <summary>
        /// 相亲者类
        /// </summary>
        public class Wooer
        {
            /// <summary>
            /// 场面话、客套话大全
            /// </summary>
            /// <param name="message"></param>
            public void Say(string message)
            {
                Console.WriteLine(message);
            }
        }

        static void Main(string[] args)
        {
            #region 不使用接口及抽象类的红娘搭线
            Matchmaker matchmaker = new Matchmaker();
            matchmaker.Teach();
            Console.Read();
            #endregion
        }
    }

View Code

运行结果如下:

以上功能实现没有问题,但是假如相亲者想要增加一点肢体动作或文艺展示来博取对方好感的话,红娘就得跟着变。于是,红娘

搭建了一个相亲平台……

下面代码演示使用接口与抽象类的红娘搭线:

class Program
    {
        /// <summary>
        /// 红娘类
        /// </summary>
        public class MatchmakerNew
        {
            string message;
            /// <summary>
            /// 场面话、客套话指导
            /// </summary>
            public void Teach(IWooer wooer)
            {
                message = "曾经有一份真挚的爱情摆在我面前……";
                wooer.Say(message);
            }
        }
        /// <summary>
        /// 相亲者接口
        /// </summary>
        public interface IWooer
        {
            /// <summary>
            /// 房子车子票子……
            /// </summary>
            string Message { get; }
            /// <summary>
            /// 能歌善舞……
            /// </summary>
            void Action();
            /// <summary>
            /// 甜言蜜语……
            /// </summary>
            /// <param name="message"></param>
            void Say(string message);
        }

        /// <summary>
        /// 男相亲者实现类
        /// </summary>
        public class ManWooer : IWooer
        {
            public string Message
            {
                get { return "嫁给我,房子车子票子啥都有。"; }
            }
            public void Action()
            {
                Console.WriteLine("野狼disco……");
            }
            public void Say(string message)
            {
                Action();
                Console.WriteLine(message + Message);
            }
        }

        /// <summary>
        /// 女相亲者实现类
        /// </summary>
        public class WomanWooer : IWooer
        {
            public string Message
            {
                get { return "娶了我,这头牛和后面的这座山都是你的。"; }
            }
            public void Action()
            {
                Console.WriteLine("相见恨晚……");
            }
            public void Say(string message)
            {
                Action();
                Console.WriteLine(message + Message);
            }
        }

        static void Main(string[] args)
        {
            #region 使用接口及抽象类的红娘搭线
            MatchmakerNew matchmakerNew = new MatchmakerNew();

            //男大为婚
            IWooer manWooer= new ManWooer();
            matchmakerNew.Teach(manWooer);
            manWooer.Say("亲:");
            Console.WriteLine();

            //女大为嫁
            IWooer womanWooer = new WomanWooer();
            matchmakerNew.Teach(womanWooer);
            womanWooer.Say("亲:");

            Console.Read();
            #endregion
        }
    }

View Code

运行结果如下:

(0)

相关推荐

  • Java学习——40、泛型

    从版本5.0开始,Java支持包含数据类型作为参数的类定义.这些参数被称为泛型. 泛型通过为类.接口及方法设置类型参数,使一个类或一个方法可以在多种类型的对象上进行操作,从而减少数据类型转换,增加软件 ...

  • .NetCore学习笔记:六、Swagger API接口文档工具

    Swagger一个优秀的Api接口文档生成工具.Swagger可以可以动态生成Api接口文档,有效的降低前后端人员关于Api接口的沟通成本,促进项目高效开发. 1.使用NuGet安装最新的包:Swas ...

  • Java学习笔记(四):理解接口 事件监听器

    一.接口 父类和子类比较,父类是一个比子类更抽象的类.这个抽象体现在两个方面:一是父类的属性和方法比子类的少:二是,子类的方法比父类的方法更加具体. 当父类非常抽象时,或者我们仅仅知道这个类的对象应该 ...

  • 【学习笔记】嵌入式技术(20):一种基于芯片的离散量接口解决方案

    2016年电子技术应用第3期 作者:杨 峰1,2,索高华3,解亚龙3 摘  要: 传统的离散量处理方案采用分立器件搭建,面积体积及重量较大,可靠性较低,BIT占用硬件资源较多,已不能满足系统小型化.高 ...

  • 一则公报案例学习笔记:对修改股东出资期限应否适用资本多数决规则的思考|审判研究

    一.问题的提出 2021年第3期<最高人民法院公报案例>刊登了鸿大(上海)投资管理有限公司与姚锦城公司决议纠纷上诉案,裁判要旨为:"公司股东滥用控股地位,以多数决方式通过修改出资 ...

  • JAVA多线程学习笔记整理

    多线程: 三种创建方法 继承Thread类,以线程运行内容重写run方法,创建Thread对象并用start方法启动该线程. (匿名内部类) (Lambda表达式) 实现Runable接口,以线程运行 ...

  • 周哥学习笔记(2021.5.8)

    心理界限存在的意义,正是为了帮助人们控制情绪进入的量,不至于太过冷漠或太过投入,让我们保持一个合适的距离与外界互动. 人没有办法只通过吸收变得更美好和丰富,它必须通过大胆的碰撞和创造.如果不能保持足够 ...

  • 【学习笔记】控制角色移动的N种方法,但都离不开重复执行

    [学习笔记]控制角色移动的N种方法,但都离不开重复执行 今天我们讲一下控制角色移动的多种方法,因为缺少操作实例,希望课下同学们结合例子好好练习. 首先,我们说一下控制角色移动的多种方法.最比较常见的就 ...

  • 胡希恕伤寒论学习笔记——42

    42.太阳病,外证未解,脉浮弱者,当以汗解,宜桂枝汤. 字面意思是说:太阳病,外证依然存在,脉是浮弱的,治疗上依然需要通过出汗的方法,这时应该用桂枝汤一类的方剂. "宜"字说明不是 ...

  • 量柱擒涨停 - 量柱战法学习笔记(2)

    四.倍量战术 1.倍量的理解 [形态特征]:与前一个交易日比较高出1倍或1倍以上,就是倍量(4倍以上为发烧柱) ; [本质特征]:体现主力强势态度,主动(倍量阳/阴)买/卖盘吸筹坚决; [位置性质]: ...