您的位置:首页 > 编程语言 > C#

2016-4-6--C#泛型变体(协变与抗变)

2016-04-06 21:27 417 查看
<转载自.net学习笔记之协变和抗变(原创)--http://www.cnblogs.com/icyJ/archive/2012/11/16/covariant.html>

对于协变和抗变的这两个词的定义,是初次接触;然而实际应用应该是从用c#语言编写代码开始的。

这两个词的理解过程非常绕,查看很多资料,再加上敲代码调试之后才逐渐有点理解它们的含义。

所谓的协变,可以理解成:父类 -> 子类。父类的对象用子类替换,也可以理解成子类当父类用。

所谓的抗变,可以理解成:子类 -> 父类。子类的对象用父类替换,也可以理解成父类当子类用。抗变也常常翻译为逆变。

在c#的语言中,很多地方的调用已经隐藏了协变和抗变的使用。函数的返回类型默认是抗变的。例如,函数Func的返回类型为string,我们可以将返回的值赋给object对象。

private void button2_Click(object sender, EventArgs e)
{
//注意这里:Func的返回类型为string, obj的类型为object, string类型继承自object
object obj = Func();
}

string Func()
{
return "这里有协变的应用";
}


函数的参数类型则默认是协变的。例如,函数Act的输入参数为object类型,实际操作中我们可以将string类型的对象传给函数。

private void button2_Click(object sender, EventArgs e)
{
string str = "这是一个string类型的实例, 函数Act的参数为object, 这里有抗变的应用";
Act(str);
}

void Act(object obj)
{
return ;
}


再看下面的代码,两者一起应用。

private void button2_Click(object sender, EventArgs e)
{
string str = "这是一个string类型的实例";
object obj = null;
obj = Both(str);
}

string Both(object obj)
{
return "协变和抗变两者都有";
}


是不是觉得非常熟悉,常常用到?所以本篇开头我说可能从开始用C#编写代码的时候就开始接触了。
接下来说说我对《c#高级编程》中的泛型接口的协变和抗变的理解。

上面提到了,c#的语法中已经定义了一些协变和抗变的应用。在泛型接口的定义中,如果泛型类型T用out关键词标注,这个泛型接口就是协变的。而且在接口的代码里面,T只能用作返回类型,不能用作参数类型。

如果泛型类型T用in关键词标注的话,这个接口就是抗变的。在接口的代码里面,T只能用作函数的参数类型,而不能用作返回类型。

<可参考:泛型接口协变逆变的几个问http://www.cnblogs.com/tenghoo/archive/2012/12/04/interface_covariant_contravariant.html>

(转载自:C#4.0泛型的协变,逆变深入剖析--http://www.2cto.com/kf/201401/273410.html

     C#4.0中有一个新特性:协变与逆变。可能很多人在开发过程中不常用到,但是深入的了解他们,肯定是有好处的。

 

     协变和逆变体现在泛型的接口和委托上面,也就是对泛型参数的声明,可以声明为协变,或者逆变。什么?泛型的参数还能声明?对,如果有了参数的声明,则该泛型接口或者委托称为“变体”。

 

List<汽车> 一群汽车 = new List<汽车>();

List<车子> 一群车子 = 一群汽车;

     显然,上面那段代码是会报错的, 虽然汽车继承于车子,可以隐士转换为车子,但是List<汽车>并不继承于List<车子>,所以上面的转换,是行不通的。

IEnumerable<汽车> 一群汽车 = new List<汽车>();

IEnumerable<车子> 一群车子 = 一群汽车;

然而这样却是可以的。那么IEnumerable接口有什么不同呢,我们且看编译器的提示:

 

 

我们可以看到,泛型参数的,用了一个“out”关键字作为声明。看来,关键是这个在起作用了。

 

 “协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型。 

 

 “逆变”则是指能够使用派生程度更小的类型。逆变,逆于常规的变。

 

协变和逆变,使用“out”,和“in”两个关键字。但是只能用在接口和委托上面,对泛型的类型进行声明

 

当声明为“out”时,代表它是用来返回的,只能作为结果返回,中途不能更改。

 

当声明为"in"时,代表它是用来输入的,只能作为参数输入,不能被返回。

 

回到上面的例子,正因为“IEnumerable”接口声明了out,所以,代表参数T只能被返回,中途不会被修改,所以,IEnumerable<车子> 一群车子 = 一群汽车;  这样的强制转换

 

是合法的,IL中实际上是作了强制转换的。

 

 IEnumerable是NET中自带的,其余还有如下接口和委托:

 

复制代码

接口:           

            IQueryable<out T>

            IEnumerator<out T>

            IGrouping<out TKey,out TElement>

            IComparer<in T>

            IEqualityComparer<in T>

            IComparable<in T>

委托:

            System.Action<in T>

            System.Func<Out Tresult>

            Predicate<in T>

            Comparison<in T>

            Converter<in TInput,out TOutput>

复制代码

此外,我们自己定义泛型接口的时候也可以使用协变和逆变,我们不妨来看一个示例,来体现协变的特征

 

    interface 接口<out T>

    {

        T 属性 { get; set; }

    }

我定义一个接口,一个具有get和set访问器的属性,然而,编译是报错的,提示:变体无效: 类型参数“T”必须为对于“test.接口<T>.属性”有效的 固定式。“T”为 协变。

 

正因为我声明了T为协变,所以,T只能被返回,不允许被修改,所以,如果去掉“set”访问器,才可以编译通过。

 

同样,如果我在“接口”中声明一个方法

 

void 方法(T t);

 

同样是会报错的,T被声明了协变,“方法(T t)”的存在就不可取。

 

复制代码

class Program

    {

        static void Main(string[] args)

        {

            接口<汽车> 一群汽车 = new 类<汽车>();

            接口<车子> 一群车子 = 一群汽车;

        }

    }

    interface 接口<out T>

    {

        T 属性

        {

            get;

        }

    }

    class 类<T> : 接口<T>

    {

        public T 属性

        {

            get { return default(T); }

        }

    }

复制代码

上面的代码是可以编译通过的,因为泛型接口“接口”声明了协变,所以“接口<车子> 一群车子 = 一群汽车;”是可以强制转换成功的,看吧,我们自己声明的同样可以实现目的。

 

 如果我把以上的代码,把“out”改成“in”呢? 显然不行,因为声明“in”规定了T不能被返回,编译无法通过的。

 

然而下面的代码是正确的:

 

复制代码

    interface 接口<in T>

    {

        void 方法(T t);

    }

    class 类<T> : 接口<T>

    {

        public void 方法(T t)

        {

           

        }

    }

复制代码

声明“in”不允许被返回,但是可以进行更改。

 

接着看:

 

复制代码

        static void Main(string[] args)

        {

            接口<车子> 一群车子 = new 类<车子>();

            接口<汽车> 一群汽车 = 一群车子;

        }

复制代码

啊,这怎么也可以啊,“车子”是父类,“汽车”是子类,汽车转换为车子正常,车子转换为汽车,这样也行?

 

其实“车子”也好,“汽车”也好,在这里都只是泛型参数,并不是他们俩之间的转换,这个基础的概念必须明白,别绕进去了。

这就是逆变。因为“接口”声明了“in”关键字,声明为逆变,让参数去接受一个相对更“弱“的类型,其实是让一个参数的类型,更加具体化,更明确化的一个过程。

<可参考:C#笔记16:协变与逆变:http://www.cnblogs.com/luminji/archive/2010/09/12/1824487.html>

<可参考:泛型接口协变逆变的几个问http://www.cnblogs.com/tenghoo/archive/2012/12/04/interface_covariant_contravariant.html>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  httpblog.csdn.netjia