• 《CLR Via C# 第3版》笔记之(十五) 接口


    接口(interface)和类(class)是CLR中应用最为广泛的两个概念。灵活的应用接口,可以构造出各种经典的设计模式。

    接口的语法并不复杂,本篇主要记录接口中一些容易忽略的地方,以及如何更好的使用接口。

    主要内容:

    • 接口的继承
    • 显式接口
    • 泛型接口和约束
    • 接口和抽象类 

    1. 接口的继承

    当子类继承父类后,父类继承的接口也一并继承了过来。如下例中的类Sub

    当子类继承父类后,子类可以再次继承父类已经继承的接口。如下例中的类Sub2

    这两者的区别在对接口方法调用,参见下面代码中的注释。

    using System;
    
    sealed class CLRviaCSharp_15
    {
        static void Main(string[] args)
        {
            Sub s = new Sub();
            // 类Sub自身的Show方法
            s.Show();
            // 类Base的Show方法,即继承自IShowMessage的Show方法
            ((Base)s).Show();
            // 类Base继承自IShowMessage的Show方法
            ((IShowMessage)s).Show();
            Console.WriteLine("=============================");
            Sub2 s2 = new Sub2();
            // 类Sub2自身的Show方法,即继承自IShowMessage的Show方法
            s2.Show();
            // 类Base的Show方法
            ((Base)s2).Show();
            // 类Sub2继承自IShowMessage的Show方法
            ((IShowMessage)s2).Show();
    
            Console.ReadKey();
        }
    }
    
    interface IShowMessage
    {
        void Show();
    }
    
    class Base : IShowMessage
    {
        public void Show()
        {
            Console.WriteLine("IShowMessage");
        }
    }
    
    
    /// <summary>
    /// 当子类继承父类后,父类继承的接口也一并继承了过来
    /// </summary>
    class Sub : Base
    {
        // 类Sub本身的Show方法,与Base中继承IShowMessage的Show无关
        public new void Show()
        {
            Console.WriteLine("Sub");
        }
    }
    
    /// <summary>
    /// 子类继承父类后,子类可以再次继承父类已经继承的接口
    /// </summary>
    class Sub2 : Base, IShowMessage
    {
        // 类Sub2继承IShowMessage的Show方法,
        // 与Base中继承IShowMessage的Show无关
        public new void Show()
        {
            Console.WriteLine("Sub2");
        }
    }  

    2. 显式接口

    在上例中,类Base继承IShowMessage之后,就不能定义与 IShowMessage中签名相同的方法了。

    如上例中,类Base无法再定义与方法Show相同签名的方法了。

    为了解决这种情况,C#中还提供了显式接口的定义方法。

    class Base : IShowMessage
    {
        public void Show()
        {
            Console.WriteLine("Base");
        }
        
        void IShowMessage.Show()
        {
            Console.WriteLine("IShowMessage");
        }
    }

    这样,如果要调用IShowMessage.Show()方法,必须将Base类的实例转型成IShowMessage才行。

            Base b = new Base();
            b.Show();
            ((IShowMessage)b).Show();

    显示接口的作用主要如下:

    1. 当两个接口有签名相同的方法时,一个类可以通过显示接口的方式来同时继承这两个接口。

    interface IShowMessage
    {
        void Show();
    }
    
    interface IPrintMessage
    {
        void Show();
    }
    
    class Base : IShowMessage, IPrintMessage
    {
        public void Show()
        {
            Console.WriteLine("Base");
        }
        
        void IShowMessage.Show()
        {
            Console.WriteLine("IShowMessage");
        }
    
        void IPrintMessage.Show()
        {
            throw new NotImplementedException();
        }
    }

    2. 通过显式接口来增强类型安全性,从而减少装箱操作,提高性能

    首先是实现隐式接口的例子:

    using System;
    
    sealed class CLRviaCSharp_15
    {
        static void Main(string[] args)
        {
            Base b = new Base();
            b.Show(10);
    
            Console.ReadKey();
        }
    }
    
    interface IShowMessage
    {
        void Show(Object o);
    }
    
    class Base : IShowMessage
    {
        #region IShowMessage Members
    
        public void Show(object o)
        {
            Console.WriteLine(o.ToString());
        }
    
        #endregion
    }

    在调用b.Show(10)时发生装箱操作,通过以下的IL代码(IL_00a)可以看出

    .method private static hidebysig 
    	void Main (
    		string[] args
    	) cil managed 
    {
    	// Method begins at RVA 0x217c
    	// Code size 28 (0x1c)
    	.maxstack 2
    	.entrypoint
    	.locals init (
    		[0] class Base b
    	)
    
    	IL_0000: nop
    	IL_0001: newobj instance void Base::.ctor()
    	IL_0006: stloc.0
    	IL_0007: ldloc.0
    	IL_0008: ldc.i4.s 10
    	IL_000a: box int32
    	IL_000f: callvirt instance void Base::Show(object)
    	IL_0014: nop
    	IL_0015: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
    	IL_001a: pop
    	IL_001b: ret
    } // End of method CLRviaCSharp_15.Main

    通过显示实现接口,可以在类Base中定义输出int型的Show方法,代码如下:

    using System;
    
    sealed class CLRviaCSharp_15
    {
        static void Main(string[] args)
        {
            Base b = new Base();
            b.Show(10);
    
            Console.ReadKey();
        }
    }
    
    interface IShowMessage
    {
        void Show(Object o);
    }
    
    class Base : IShowMessage
    {
        public void Show(int i)
        {
            Console.WriteLine(i.ToString());
        }
    
        #region IShowMessage Members
    
        void IShowMessage.Show(object o)
        {
            throw new NotImplementedException();
        }
    
        #endregion
    }

    查看Main函数的IL代码,已经没有了之前的装箱操作

    .method private static hidebysig 
    	void Main (
    		string[] args
    	) cil managed 
    {
    	// Method begins at RVA 0x217c
    	// Code size 23 (0x17)
    	.maxstack 2
    	.entrypoint
    	.locals init (
    		[0] class Base b
    	)
    
    	IL_0000: nop
    	IL_0001: newobj instance void Base::.ctor()
    	IL_0006: stloc.0
    	IL_0007: ldloc.0
    	IL_0008: ldc.i4.s 10
    	IL_000a: callvirt instance void Base::Show(int32)
    	IL_000f: nop
    	IL_0010: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
    	IL_0015: pop
    	IL_0016: ret
    } // End of method CLRviaCSharp_15.Main

    显式接口还有一点需要注意的地方是,显式接口不能由派生类调用。

    3. 泛型接口和约束

    泛型是之前看过的概念,在接口中使用泛型,同样可以获得泛型带给我们的种种好处。

    1. 提供类型安全性

    比如IComparable接口和IComparable<Int32>接口相比,后者提供了类型安全检查。

            int x = 1;
            IComparable c1 = x;
            // 编译通过,但是运行时异常
            c1.CompareTo("2");
    
            IComparable<Int32> c2 = x;
            // 编译不通过,类型不匹配
            c2.CompareTo("2");

    2. 减少装箱次数

    还是用IComparable接口和IComparable<Int32>接口做比较。

            int x = 1, y = 2;
            // 此处装箱一次 (x装箱)
            IComparable c1 = x;
            // 此处装箱一次 (y装箱)
            c1.CompareTo(y);
    
            // 此处装箱一次 (x装箱)
            IComparable<Int32> c2 = x;
            // 此处不用装箱,因为类型参数T为Int32
            c2.CompareTo(y);

    3. 一个类可以实现同泛型参数类型不同的同一个接口,如下所示

    class MyClass : IComparable<Int32>,IComparable<string>
    {
        #region IComparable<int> Members
    
        public int CompareTo(int other)
        {
            throw new NotImplementedException();
        }
    
        #endregion
    
        #region IComparable<string> Members
    
        public int CompareTo(string other)
        {
            throw new NotImplementedException();
        }
    
        #endregion
    }

    4. 接口和基类

    “接口和基类的关系,以及何时使用接口,何时使用基类。”是在设计时经常需要考虑的问题。

    已经有很多文章对此进行了讨论,这里要说的是这两件事可以同时做。

    即:定义一个接口,同时提供一个实现了这个接口的基类

    .net Framework中的就有这样的例子,比如IComparable接口就有Comparable类提供的默认实现。

    比如下面的例子,即使MyClass中没有实现IShowMessage的代码也无所谓,因为ShowMessage提供了默认实现。

    interface IShowMessage
    {
        void Show(Object o);
    }
    
    class ShowMessage : IShowMessage
    {
        #region IShowMessage Members
    
        public void Show(object o)
        {
            Console.WriteLine(o.ToString());
        }
    
        #endregion
    }
    
    class MyClass : ShowMessage , IShowMessage
    {
    }
  • 相关阅读:
    正则表达式体会
    checkbox、全选反选,获取值
    弹出窗体值回调
    页面点击任意js事件,触发360、IE浏览器新页面
    XML增、删、改
    面试题
    行列转换
    DataTable 和Json 字符串互转
    前台js与后台方法互调
    文件与base64二进制转换
  • 原文地址:https://www.cnblogs.com/wang_yb/p/2119737.html
Copyright © 2020-2023  润新知