真心求知道协变逆变如何体现出代码重用?

钟景华 2012-03-30 11:08:28

//动物类
public abstract class Animal
{
public string str = "Animal";

public void animal()
{

}

public void dog()
{

}

}

//继承动物的狗
public class Dog : Animal
{
public string str = "Dog";

public void animal()
{

}

public void dog()
{

}
}


//使用协变和逆变
static void Main(string[] args)
{
Dog dog = new Dog();
Animal animal = dog;

List<Dog> dogs = new List<Dog>();
dogs.Add(dog);

//下面进行协变:依然会调用自身的方法。这个协变到底有什么作用?直接new Animal本身,直接用他的不就可以了么?干嘛还要这么麻烦呢?
IEnumerable<Dog> someDogs = dogs;
IEnumerable<Animal> someAnimals = someDogs;
someAnimals.ToList()[0].animal();
someDogs.ToList()[0].animal();

//public delegate void Action<in T>(T obj);
//下面进行逆变,同理,为什么这样的动作不在模型类直接在动物类添加“叫”,非要在这里+个方法,然后让狗调用这么麻烦呢?
Dog aDog = new Dog();

Action<Animal> actionAnimal = new Action<Animal>(a => { Console.Write("叫"); Console.Read(); });
Action<Dog> actionDog = actionAnimal;
actionDog(aDog);



}

...全文
231 24 打赏 收藏 转发到动态 举报
写回复
用AI写文章
24 条回复
切换为时间正序
请发表友善的回复…
发表回复
threenewbee 2012-04-08
  • 打赏
  • 举报
回复
唉,为什么有的人学编程就那么累呢。

协变和逆变是很自然的东西。这好比原先没有一个叫平板电脑的东西。但是它的出现,马上大家就觉得这东西好用。没有人会问,为什么平板电脑体现了好用,或者这东西以前没有,所以我不会用这样的问题。

难道你不觉得
既然 People cnp = new ChinesePeople(); 成立

List<Prople> cnp = new List<ChinesePeople>(); 是很自然的事情。

不过遗憾的是,在C# 4以前这么写不行。现在行了,难道还有什么需要研究的呢?
orochiheart 2012-04-08
  • 打赏
  • 举报
回复
逆变? 斜变? 是神马意思
钟景华 2012-04-08
  • 打赏
  • 举报
回复
[Quote=引用 18 楼 的回复:]

IEnumerable<Animal> someAnimals = someDogs;

这个代码就是协变。.net4.0以前是不能这样写代码的,你不知道以前是怎样的,当然那就不觉得这是什么进步。
[/Quote]


IEnumerable这个是集合吧,单对象协变不了吗?
就是 dog = animal ;
钟景华 2012-04-08
  • 打赏
  • 举报
回复
[Quote=引用 16 楼 的回复:]

变体是指类型转换关系的映射,不能完成你想要的功能。

比如说string->object的转换,可以映射到string[]和object[]上:string[]->object[],这种映射能力叫做变体。
映射后的转换关系string[]->object[]和原始的转换关系string->object方向相同,这种变体叫做“协变”;如果方向相反,就叫“逆变”。
string->objec……
[/Quote]

Animal和Cat不能互相转换?只能针对类型?对象之前不能转换么
  • 打赏
  • 举报
回复
至于你说为什么要为对象声明 IEnumerable<Animal> 类型而不直接声明为 IEnumerable<Dog>,为什么要有个 Action<Animal> 而不直接把委托声明为 Action<Dog>,那不就是为了刻意给你做演示嘛!
  • 打赏
  • 举报
回复
以前,你要想把someDogs赋值给someAnimals ,根本不可能能。你只能写代码先 new List<Animal>(),然后再逐一将someDogs集合中的元素一个一个地Add进someAnimals里边。

  • 打赏
  • 举报
回复
IEnumerable<Animal> someAnimals = someDogs;

这个代码就是协变。.net4.0以前是不能这样写代码的,你不知道以前是怎样的,当然那就不觉得这是什么进步。
hhddzz 2012-04-07
  • 打赏
  • 举报
回复
实际上就是在原来需要包装的地方现在不需要了
hhddzz 2012-04-07
  • 打赏
  • 举报
回复
变体是指类型转换关系的映射,不能完成你想要的功能。

比如说string->object的转换,可以映射到string[]和object[]上:string[]->object[],这种映射能力叫做变体。
映射后的转换关系string[]->object[]和原始的转换关系string->object方向相同,这种变体叫做“协变”;如果方向相反,就叫“逆变”。
string->object的转换关系可以映射为IComparer<object>->IComparer<string>,和原来的方向相反,所以它是“逆变”。

上面这种string->object的转换,本质上是“派生类引用到基类引用的转换”。除了这种转换,还有其他转换,比如基元类型之间的转换,像byte->short->int->long,以及用户自定义的转换。在.NET中,只有派生类引用到基类引用的转换支持变体,其他两种不支持。
所以虽然byte可以隐式转换为int,但是byte[]不能转换为int[];同样,IEnumerable<byte>不能转换为IEnumerable<int>。

变体不仅存在于泛型中,也存在于其他地方,比如上面例子中的数组,另外就是委托和函数的绑定。

至于变体体现代码重用这个很明显啊,它可以将原有类型的转换自动映射到基于原有类型的新类型上,这样你同一份代码就可以用在更多的地方。(如果没有变体,这些新类型之间就没有转换关系,需要为每种类型单独写代码。)


钟景华 2012-04-06
  • 打赏
  • 举报
回复
[Quote=引用 14 楼 的回复:]

引用 13 楼 的回复:

引用 7 楼 的回复:

比如这个方法:
C# code
void ProcessControls(IEnumerable<Control> ctrs){ }

没有协变,你就还得另外创建个实现了IEnumerable<Control>的集合。

再比如:
C# code
Comparison<Control> ControlCompari……
[/Quote]

非常感谢你的耐心回答!!!!!!但是貌似这些都是转换类中的某个方法??我看的还是很朦胧...即使以你的例子来代码写还是很朦胧...



那我想问个问题
现在我的项目
有父抽象类Animal,有属性:吃,方法:叫
然后有2个子类:
猫:Cat,有属性:抓
狗:Dog,有属性:咬

我想在程序初始化中先 Animal animal = null
然后在判断条件中如果是猫:
animal = new Cat;
如果是狗
animal = new Dog;

但是,我想通过逆变来让animal可以使用到Cat或者Dog的属性。也就是整个代码中,我依然使用animal,而不用实例化cat或者dog来增加代码量
hhddzz 2012-03-31
  • 打赏
  • 举报
回复
比如这个方法:
 void ProcessControls(IEnumerable<Control> ctrs){ }

没有协变,你就还得另外创建个实现了IEnumerable<Control>的集合。

再比如:
Comparison<Control> ControlComparison =
(ctr1, ctr2) => ctr1.Left - ctr2.Left;

List<TextBox> TextBoxList = new List<TextBox>()
{new TextBox(){Left=30},new TextBox(){Left=22}};
List<Label> LabelList = new List<Label>();

TextBoxList.Sort(ControlComparison);
LabelList.Sort(ControlComparison);

现在ControlComparison很简单,你可以每次在Sort方法里复制一份,但是如果它的逻辑很复杂就不行了。
或者要使用一个别人提供的,不知道内部实现的IComparer<Control>。
nonocast 2012-03-31
  • 打赏
  • 举报
回复
嘿嘿,没碰上也不必硬来
遇到了你自然就懂了
即使你懂了你没碰上也是没用的
cheng2005 2012-03-31
  • 打赏
  • 举报
回复
[Quote=引用 10 楼 的回复:]

引用 8 楼 的回复:

楼主先把继承搞明白再研究这些东西吧。
你看看你自己写的Animal和Dog,除了Dog继承了Animal之外一点点关系都没有。


额。。。我知道你的意思,你看下这个,主要我是想实验一下,他们的animal的方法通过协变,究竟变了没有。

someAnimals.ToList()[0].animal();
someDogs.ToList()[0].a……
[/Quote]
按照你的写法Animal和Dog根本就没什么关系,所以声明类型,调用的就是哪个类型的方法。
所以someAnimals里面是Animal,调用的就是Animal的方法。
someDogs里面是Dog,调用的就是Dog的方法。
虽然都是同一个对象。
nonocast 2012-03-31
  • 打赏
  • 举报
回复
[Quote=引用 9 楼 的回复:]

引用 6 楼 的回复:

嘿嘿,没碰上也不必硬来
遇到了你自然就懂了
即使你懂了你没碰上也是没用的


如果能了解到,那么可能以后设计底层方面或者代码复用可能都可以想到并且用上。。。
[/Quote]

这个是用来解决设计问题,而不是根据这个能创建设计...
你反过来想,ruby/python/js会有协变和逆变吗?
钟景华 2012-03-31
  • 打赏
  • 举报
回复
[Quote=引用 8 楼 的回复:]

楼主先把继承搞明白再研究这些东西吧。
你看看你自己写的Animal和Dog,除了Dog继承了Animal之外一点点关系都没有。
[/Quote]

额。。。我知道你的意思,你看下这个,主要我是想实验一下,他们的animal的方法通过协变,究竟变了没有。

someAnimals.ToList()[0].animal();
someDogs.ToList()[0].animal();
钟景华 2012-03-31
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 的回复:]

嘿嘿,没碰上也不必硬来
遇到了你自然就懂了
即使你懂了你没碰上也是没用的
[/Quote]

如果能了解到,那么可能以后设计底层方面或者代码复用可能都可以想到并且用上。。。
cheng2005 2012-03-31
  • 打赏
  • 举报
回复
楼主先把继承搞明白再研究这些东西吧。
你看看你自己写的Animal和Dog,除了Dog继承了Animal之外一点点关系都没有。
hhddzz 2012-03-31
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 的回复:]

引用 7 楼 的回复:

比如这个方法:
C# code
void ProcessControls(IEnumerable<Control> ctrs){ }

没有协变,你就还得另外创建个实现了IEnumerable<Control>的集合。

再比如:
C# code
Comparison<Control> ControlComparison =
(ctr1, ctr……
[/Quote]
复制就是那个意思
----------------------------------------
这个是委托逆变。泛型在接口和委托两个地方支持变体(逆变和协变)
比如说TextBoxList,它的类型是List<TextBox>,所以Sort方法的参数类型就是Comparison<TextBox>(这是一个委托类型)。如果没有逆变,Comparison<Control>就不能转化为Comparison<TextBox>,也就无法使用ControlComparison委托对象。IComparer<T>同理,List<TextBox>的Sort方法要求参数类型为IComparer<TextBox>,如果没有逆变,就不能用一个IComparer<Control>对象作为它的参数。

当然,你可以把比较器类实现为开放类型,让它可以产生其他类型的比较器。比如:
class MyControlComparer<T> : IComparer<T> where T:System.Windows.Forms.Control {}
当你需要排序任何Control派生类集合的时候可以方便地创建一个对应类型的比较器,但是你仍然需要创建新的比较器对象,而不能复用已有的比较器对象(或许这个比较器有很多的配置选项)。

而如果是封闭实现
class MyControlComparer : IComparer<System.Windows.Forms.Control> { }
那就像上面所说的,没办法使用了。
钟景华 2012-03-31
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 的回复:]

比如这个方法:
C# code
void ProcessControls(IEnumerable<Control> ctrs){ }

没有协变,你就还得另外创建个实现了IEnumerable<Control>的集合。

再比如:
C# code
Comparison<Control> ControlComparison =
(ctr1, ctr2……
[/Quote]



不好意思。。。我看了很久,并且照着代码敲打,感觉没看出逆变和协变的效果...
比如这个
C# code
private void ProcessControls(IEnumerable<Control> ctrs)
{

}

private void Action()
{
List<TextBox> textboxs = new List<TextBox>();
List<Label> LabelList = new List<Label>();
ProcessControls(textboxs);
ProcessControls(LabelList);
}

难道这样就是协变了吗?如果这样就是协变了话,那么ProcessControls方法也就有比较大的限制吧?只能应用于textboxs和LabelList的共性


还有你说的第2个:
C# code
//如果这个很复杂的一个方法,然后下面的两个控件都是比较
Comparison<Control> ControlComparison = (ctr1, ctr2) => ctr1.Left - ctr2.Left;

List<TextBox> TextBoxList = new List<TextBox>() { new TextBox() { Left = 30 }, new TextBox() { Left = 22 } };
List<Label> LabelList = new List<Label>();

TextBoxList.Sort(ControlComparison);
LabelList.Sort(ControlComparison);


这个是逆变吗?这个我看不懂= =...
你所说的“你可以每次在Sort方法里复制一份”
是指这样吗
TextBoxList.Sort((ctr1, ctr2) => ctr1.Left - ctr2.Left);
LabelList.Sort((ctr1, ctr2) => ctr1.Left - ctr2.Left);




钟景华 2012-03-30
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 的回复:]

逆变和协变是针对泛型接口的上下层次隐式转换, 在.net4 以前是不允许的,
而普通类的上下转换一直都支持.

.net4 加入这个功能就是为了让泛型类也保持这样的便利性.
[/Quote]

谢谢你的回答!那我想问下泛型集合能上下层次隐式转换的话,有没有相关的例子来体现这个代码复用呢
加载更多回复(4)

110,539

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧