C#之接口

声明接口

接口声明只能包含如下类型的非静态成员函数:

  • 方法
  • 属性
  • 事件
  • 索引器

接口的访问性与接口成员的访问性有一定的区别:

  • 接口声明可以有任何的访问修饰符:public,protected,internal,private
  • 然而,接口的成员是隐式public的,不允许有任何的访问修饰符,包括public,这意味着实现接口的类实例转换为接口后,接口可以调用接口所规定的方法

接口是引用类型

接口不仅仅是类或结构要实现的成员列表,它也是引用类型,不能通过直接通过类对象的成员访问接口,然而,可以通过把类对象引用强制转换为接口类型来获取指向接口的引用,一旦有了接口的引用,就可以使用点号来获取接口引用的实例。

可以用as运算符来将实现接口的类转换为接口的引用,如果类没有实现接口,表达式返回null,而不是抛出异常。

实现多个接口

类或接口可以实现任意数量的接口,所有实现的接口必须列在基类列表中,并以逗号分隔。

实现具有重复成员的接口

由于类可以实现任意数量的接口,有可能两个或多个接口成员都有相同的签名和返回类型。如果一个类实现了多个接口,并且其中一些接口有相同签名和返回类型,那么类可以实现单个成员来满足所有包含重复成员的接口。如果类实现了多个接口,可以获取每一个接口的独立引用。

    interface IIfc1
    {
        void PrintOut(string s);
    }
    interface IIfc2
    {
        void PrintOut(string s);
    }

    class MyClass : IIfc1, IIfc2
    {
        public void PrintOut(string s)
        {
            Console.WriteLine("Calling through:{0}", s);
        }
    }
    class Program
    {

     public static void Main()
        {
            var mc = new MyClass();
            IIfc1 ifc1 = mc as IIfc1;
            IIfc2 ifc2 = mc as IIfc2;

            mc.PrintOut("object");
            ifc1.PrintOut("IIfc1");
            ifc2.PrintOut("IIfc2");

        }

    }

image-20210919230621750

派生成员作为实现

实现接口的类可以从它的基类继承实现的代码。

interface IIfc1P { void PrintingOUt(string s); }
    class MyBase
    {
        public void PrintingOUt(string s)
        {
            Console.WriteLine($"Mybasing calling{s}");
        }
    }
    class Derived : MyBase, IIfc1P
    {

    }
    class Program
    {

        public static void Main()
        {

            var deri = new Derived();
            IIfc1P iifc = (IIfc1P)deri;
            iifc.PrintingOUt("iifc1P");

        }

image-20210919095549092

显式接口成员实现

单个类可以实现多个接口需要的所有成员(接口所指定的方法,属性,事件,索引器),尤其对于实现重复成员的接口,不想实现单个成员来满足这些包含重复成员的接口,想为每一个接口分离,该怎么做?在这种情况下,可以创建显式接口成员实现,它具有如下特性:

  • 与所有接口实现相似,位于实现了接口的类或结构中
  • 使用限定接口名称来声明,由接口名称和成员名称以及它们中间的点分隔符号构成
  • 使用了限定接口名称后,类要有自己的相同签名的成员,必须单独实现,但不是必需的。
interface IIfc1 { void PrintOut(string s); }
    interface IIfc2{ void PrintOut(string s);}
    class MyClass : IIfc1, IIfc2
    {
        void IIfc1.PrintOut(string s)//限定接口名称
        {
            Console.WriteLine("IIfc1:{0}", s);
        }
        void IIfc2.PrintOut(string s)//限定接口名称
        {
            Console.WriteLine("IIfc2:{0}", s);
        }
        public void PrintOut(string s) //类单独实现
        {
            Console.WriteLine("MyClass:{0}", s);
        }
    }
    class Program
    {
        public static void Main()
        {
            var mc = new MyClass();
            IIfc1 iifc1 = (IIfc1)mc;
            IIfc2 iifc2 = (IIfc2)mc;
            iifc1.PrintOut("interface 1");
            iifc2.PrintOut("interface 2");
            mc.PrintOut("myclass");

        }
    }

image-20210919103422184

访问显示接口成员实现

显式接口的成员实现只能通过指向接口的引用来访问,也就是说其他的类成员都不可以直接访问它们,当然派生类的成员也不能直接访问它,也必须通过指向接口的引用来访问

    interface IIfc1 { void PrintOut(string s); }
    class MyClass : IIfc1
    {
        void IIfc1.PrintOut(string s)
        {
            Console.WriteLine("IIFC1:{0}",s);
        }

        public void Method1()
        {
            //PrintOut("..."),不可以直接访问
            //this.PrintOut("..."), 不可以直接访问
            ((IIfc1)this).PrintOut("....");//必须转换为接口类型,通过接口来调用显式接口
        }
    }
    class Derived : MyClass
    {
        public void Method2()
        {
            ((IIfc1)this).PrintOut("this is derived");
        }
    }
    
    class Program
    {
        public static void Main()
        {
            var mc = new MyClass();
            mc.Method1();
            var dr = new Derived();
            dr.Method1();
            dr.Method2();
            var mcc = (MyClass)dr;
            mcc.Method1();


        }
    }

image-20210919105603197

接口可以继承接口

interface IDataRetrieve{int GetData();}
interface IDataStore{void SetData(int x);}
interface IData:IDataRetrieve,IDataStore{}

不同类实现一个接口的示例

    interface ILiveBirth { string BabyCalled(); }
    class Animal { }
    class Cat : Animal, ILiveBirth
    {
        string ILiveBirth.BabyCalled()
        {
            return "Kitten";
        }
    }
    class Dog : Animal, ILiveBirth
    {
        string ILiveBirth.BabyCalled()
        {
            return "Puppy";
        }
    }

    class Bird : Animal
    {

    }
    
    class Program
    {
     public static void Main()
        {
            Animal[] animalArray = new Animal[3];
            animalArray[0] = new Cat();
            animalArray[1] = new Bird();
            animalArray[2] = new Dog();
            foreach (var a in animalArray)
            {
                if (a is ILiveBirth)
                {
                    var b = a as ILiveBirth;
                    Console.WriteLine($"{b.BabyCalled()}");
                }
            }
        }


        
    }

image-20210919161504528

泛型接口

  • 与泛型类的结合
interface IMyIfc<T>
    {
        T ReturnIt(T invalue);
    }
    
    class Simple<S> : IMyIfc<S>
    {
        public S ReturnIt(S invalue)
        {
            return invalue;
        }
    }
    class Program
    {
     public static void Main()
        {
            var triInt = new Simple<int>();
            var triString = new Simple<string>();
            Console.WriteLine($"{triInt.ReturnIt(100)}");
            Console.WriteLine($"{triString.ReturnIt("Yes it is generic interface")}");
        }


        
    }

image-20210919162905656

  • 实现不同类型参数的泛型接口是不同的接口,可以在非泛型类中实现泛型接口
    interface IMyIfc<T>
    {
        T ReturnIt(T inValue);
    }

    class Simple : IMyIfc<int>, IMyIfc<string>
    {
        public int ReturnIt(int test)
        {
            return test;
        }
        public string ReturnIt(string test) //只要签名与接口签名一致即可,也就是说函数参数名可以不必与接口规定的参数名一致
        {
            return test;
        }
    }
    class Program
    {
     public static void Main()
        {
            var trivial = new Simple();
            Console.WriteLine($"{trivial.ReturnIt(100)},and {trivial.ReturnIt("this is also generic interface")}");
        }

image-20210919163508803

  • 泛型接口的实现必须唯一

实现泛型接口时,必须保证类型实参组合不会在类型中产生两个重复的接口。

​ 错误一

image-20210919164914824

泛型类中传入类型参数,泛型接口才可以使用该类型参数,否则就会出现上述错误

更正后,又一错误,即类型实参组合会在类型中产生两个重复的接口。

image-20210919165120104

接口的协变与逆变

class Animal
    { public string Name;  
    }
class Dog : Animal { }
    
    interface IMyIfc<out T> //协变关键字out,也规定了必须返回T类型
    {
        T GetFirst();
    }

    interface IMyIfc2<in T> //逆变关键字in,也规定了必须以T类型为输入参数
    {
        void PrintOut(T invalue);
    }
    class Program
    {
        static void DoSomething(IMyIfc<Animal> returner) //以接口为输入参数,接口的类型参数为Animal
        {
            Console.WriteLine(returner.GetFirst().Name);//调用接口的GetFirst方法,为此,不得不将实现接口的类转换为接口
        }

        static void ToTest(IMyIfc2<Dog> dogtester)
        {
            var d = new Dog() { Name = "jack" };
            dogtester.PrintOut(d);
        }

     public static void Main()
        {
            //协变
            var dogReturner =new SimpleReturn<Dog>();
            dogReturner.items[0] = new Dog { Name = "Avonlea" };
            var animalReturner = dogReturner as IMyIfc<Animal>;
            Console.WriteLine(animalReturner.GetFirst().Name);
            DoSomething(animalReturner);
            
            //逆变
            var animalTest = new TestReturn();
            var dogTest = animalTest as IMyIfc2<Dog>;
            ToTest(dogTest);

            //协变
            var test2 = new TestReturn2();
            var ani = test2 as IMyIfc<Animal>;
            Console.WriteLine(ani.GetFirst().Name);
            



        }

    class SimpleReturn<T> : IMyIfc<T> //实现接口的类
        {
            public T[] items = new T[2];
            public T GetFirst()
            {
                return items[0];
            }
        }

    class TestReturn : IMyIfc2<Animal>
        {
            
            public void PrintOut(Animal ani)
            {
                Console.WriteLine("IMyIfc2:"+ani.Name);
            }
        }

    class TestReturn2 : IMyIfc<Dog>
        {
            Dog cuteDog = new Dog { Name = "Jack" };
            public Dog GetFirst()
            {
                return cuteDog;
            }
        }
        

        
    }

image-20210919220257188

协变是以out为关键字,逆变以in为关键字。

T0T1的基类,若记泛型类型T0的接口或者委托为F(T0),则

协变就是可以将F(T1)转换为F(T0),因为协变是对输出有要求,如果期待是输出派生类T1,实际输出的是基类T0,是不会有问题的,也是类型安全的,即可以将F(T1)转换为F(T0)

逆变就是可以将F(T0)转换为F(T1),因为逆变是对输入有要求,如果期待输入的是基类T0,实际输入的是派生类T1,是不会有问题的,也是类型安全的,即可以将F(T0)转换为F(T1)

对于接口而言,由于调用接口的方法是必须将实现接口的类转换为接口,所以必须有实现泛型接口的类,然后可以将派生类转换为类型参数是基类的接口,此为协变,将基类转换为类型参数为派生类的接口,此为逆变

口诀:out 协变,对输出有要求,派生类转基类;In逆变,对输入有要求,基类转派生类。

posted @ 2021-09-19 23:07  JohnYang819  阅读(677)  评论(0编辑  收藏  举报