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");
}
}
派生成员作为实现
实现接口的类可以从它的基类继承实现的代码。
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");
}
显式接口成员实现
单个类可以实现多个接口需要的所有成员(接口所指定的方法,属性,事件,索引器),尤其对于实现重复成员的接口,不想实现单个成员来满足这些包含重复成员的接口,想为每一个接口分离,该怎么做?在这种情况下,可以创建显式接口成员实现,它具有如下特性:
- 与所有接口实现相似,位于实现了接口的类或结构中
- 使用
限定接口名称
来声明,由接口名称和成员名称以及它们中间的点分隔符号构成 - 使用了限定接口名称后,类要有自己的相同签名的成员,必须单独实现,但不是必需的。
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");
}
}
访问显示接口成员实现
显式接口的成员实现只能通过指向接口的引用来访问,也就是说其他的类成员都不可以直接访问它们,当然派生类的成员也不能直接访问它,也必须通过指向接口的引用来访问
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();
}
}
接口可以继承接口
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()}");
}
}
}
}
泛型接口
- 与泛型类的结合
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")}");
}
}
- 实现不同类型参数的泛型接口是不同的接口,可以在非泛型类中实现泛型接口
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")}");
}
- 泛型接口的实现必须唯一
实现泛型接口时,必须保证类型实参组合不会在类型中产生两个重复的接口。
错误一
泛型类中传入类型参数,泛型接口才可以使用该类型参数,否则就会出现上述错误
更正后,又一错误,即类型实参组合会在类型中产生两个重复的接口。
接口的协变与逆变
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;
}
}
}
协变是以out
为关键字,逆变以in
为关键字。
T0
是T1
的基类,若记泛型类型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逆变,对输入有要求,基类转派生类。