理解C#泛型运作原理 (4)

类的函数成员:属性,字段,索引,构造器,运算符只能引入类声明的类型参数,不能够声明,唯有方法这一函数成员具备声明和引用类型参数两种功能,由于具备声明功能,因此可以声明和委托一样的类型参数并且引用它,这也体现了方法的多态性

多态的继承

父类和实现类或接口的接口都可以是实例化类型,直接看代码:

interface IFooBase<IBaseT>{} interface IFoo<InterfaceT>: IFooBase<string> { void InterfaceMenthod(InterfaceT interfaceT); } class FooBase<ClassT> { } class Foo<ClassT, ClassT1>: FooBase<ClassT>,IFoo<StringBuilder>{}

我们可以通过例子看出:

由于Foo的基类FooBase定义的和Foo有着共享的类型参数ClassT,因此可以在继承的时候不实例化类型

而Foo和IFoo接口没定义相同的类型参数,因此可以在继承的时候实例化出接口的类型参数StringBuild出来

IFoo和IFooBase没定义相同的类型参数,因此可以在继承的时候实例化出接口的类型参数string出来

上述都体现出继承的多态性

多态的递归

我们定义如下一个类和一个方法,且不会报错:

class D<T> { } class C<T> : D<C<C<T>>> { void Foo() { var foo = new C<C<T>>(); Console.WriteLine(foo.ToString()); } }

因为T能在实例化的时候确定其类型,因此也支持这种循环套用自己的类和方法的定义

四.泛型的约束 where的约束

我们先上代码:

class FooBase{ } class Foo : FooBase { } class someClass<T,K> where T:struct where K :FooBase,new() { } static void TestConstraint() { var someClass = new someClass<int, Foo>();//通过编译 //var someClass = new someClass<string, Foo>();//编译失败,string不是struct类型 //var someClass = new someClass<string, long>();//编译失败,long不是FooBase类型 }

再改动下Foo类:

class Foo : FooBase { public Foo(string str) { } } static void TestConstraint() { var someClass = new someClass<int, Foo>();//编译失败,因为new()约束必须类含有一个无参构造器,可以再给Foo类加上个无参构造器就能编译通过 }

 我们可以看到,通过where语句,可以对类型参数进行约束,而且一个类型参数支持多个约束条件(例如K),使其在实例化类型参数的时候,必须按照约束的条件对应实例符合条件的类型,而where条件约束的作用就是起在编译期约束类型参数的作用

out和in的约束

 说到out和in之前,我们可以说下协变和逆变,在C#中,只有泛型接口和泛型委托可以支持协变和逆变

协变

我们先看下代码:

class FooBase{ } class Foo : FooBase { } interface IBar<T> { T GetValue(T t); } class Bar<T> : IBar<T> { public T GetValue(T t) { return t; } } static void Test() { var foo = new Foo(); FooBase fooBase = foo;//编译成功 IBar<Foo> bar = new Bar<Foo>(); IBar<FooBase> bar1 = bar;//编译失败 }

 这时候你可能会有点奇怪,为啥那段代码会编译失败,明明Foo类可以隐式转为FooBase,但作为泛型接口类型参数实例化却并不能呢?使用out约束泛型接口IBar的T,那段代码就会编译正常,但是会引出另外一段编译报错:

interface IBar<out T> { T GetValue(string str);//编译成功 //T GetValue(T t);//编译失败 T不能作为形参输入,用out约束T支持协变,T可以作为返回值输出 } IBar<Foo> bar = new Bar<Foo>(); IBar<FooBase> bar1 = bar;//编译正常

因此我们可以得出以下结论:

由于Foo继承FooBase,本身子类Foo包含着父类允许访问的成员,因此能隐式转换父类,这是类型安全的转换,因此叫协变

在为泛型接口用out标识其类型参数支持协变后,约束其方法的返回值和属性的Get(本质也是个返回值的方法)才能引用所声明的类型参数,也就是作为输出值,用out很明显的突出了这一意思

而支持迭代的泛型接口IEnumerable也是这么定义的:

public interface IEnumerable<out T> : IEnumerable { new IEnumerator<T> GetEnumerator(); } 逆变

我们将上面代码改下:

class FooBase{ } class Foo : FooBase { } interface IBar<T> { T GetValue(T t); } class Bar<T> : IBar<T> { public T GetValue(T t) { return t; } } static void Test1() { var fooBase = new FooBase(); Foo foo = (Foo)fooBase;//编译通过,运行时报错 IBar<FooBase> bar = new Bar<FooBase>(); IBar<Foo> bar1 = (IBar<Foo>)bar;//编译通过,运行时报错 }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zygfds.html