惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threatpost
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Security Affairs
N
News and Events Feed by Topic
T
Tenable Blog
P
Proofpoint News Feed
W
WeLiveSecurity
Simon Willison's Weblog
Simon Willison's Weblog
Google DeepMind News
Google DeepMind News
C
CERT Recently Published Vulnerability Notes
Help Net Security
Help Net Security
I
Intezer
T
Threat Research - Cisco Blogs
S
Secure Thoughts
C
Cyber Attacks, Cyber Crime and Cyber Security
L
Lohrmann on Cybersecurity
AWS News Blog
AWS News Blog
Google Online Security Blog
Google Online Security Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Know Your Adversary
Know Your Adversary
Project Zero
Project Zero
The Hacker News
The Hacker News
Security Archives - TechRepublic
Security Archives - TechRepublic
T
Tor Project blog
N
News | PayPal Newsroom
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Hacker News - Newest:
Hacker News - Newest: "LLM"
A
Arctic Wolf
Forbes - Security
Forbes - Security
O
OpenAI News
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Security Latest
Security Latest
P
Palo Alto Networks Blog
S
Schneier on Security
S
Securelist
C
Cybersecurity and Infrastructure Security Agency CISA
H
Heimdal Security Blog
V
Vulnerabilities – Threatpost
www.infosecurity-magazine.com
www.infosecurity-magazine.com
博客园_首页
T
Troy Hunt's Blog
Latest news
Latest news
Recent Announcements
Recent Announcements
MyScale Blog
MyScale Blog
人人都是产品经理
人人都是产品经理
L
LINUX DO - 热门话题
M
MIT News - Artificial intelligence
N
Netflix TechBlog - Medium
V
Visual Studio Blog
H
Hacker News: Front Page

博客园 - maledong

如何通过反射调用扩展方法? JQuery学习笔记——nt-child的使用 - maledong - 博客园 求最大公约数与最小公倍数的算法 ASP.NET专题研究——角色和Profile ASP.NET专题研究——用户登录、注册和发送密码 NET基本探究——事件 ASP.NET专题研究——登录权限 C#4.0和VS2010新特性(三) C#4.0和VS2010新特性(一) F# 快乐之旅(一)——F#尝鲜 MVC专题研究(五)——数据验证 MVC专题研究(四)——Html中的扩展方法 MVC专题研究(三)——数据绑定和传送 MVC专题研究(二)——神奇的URL MVC2专题研究(一)——用MVC组织管理您的家庭收入 典型算法及应用——“递归法”探究 ASP.NET专题研究——资源文件的使用 NET基本探究系列——委托 NET常见类系列探究——序列化和反序列化的应用
C#4.0和VS2010新特性(二)
maledong · 2010-04-16 · via 博客园 - maledong

6)协变和反变(Co-variant & Crop-variant)

这是VS2010新增的一个内容,用于在编译的时候确认是否允许不同类型的泛型接口之间是否存在转换的问题。

为了了解“协变”和“反变”的概念,我们先看一个例子:

假设我们定义了一个接口和若干类:

class Father

    {

        public virtual void Say()

        {

            Console.WriteLine("Father");

        }

    }

    class Son : Father

    {

        public override void Say()

        {

            Console.WriteLine("Son");

        }

    }

   class Son2 : Father

    {

        public override void Say()

        {

            Console.WriteLine("Son2");

        }

    }

Interface  InAndOut<T, V>

             {

             void Input(V value);

            T Output();

}

class Program<T,V>:InAndOut<T,V>

    {

        private object value = default(V);

        public T Output()

        {

            return (T)value;

        }

        public void Input(V tv)

        {

            value = tv;

        }

    }

又假如我们已经实例化两个接口的实例:

InAndOut<Father, Son> iobj1 = new Program < Father, Son >();

InAndOut<Son, Father> iobj2 = new Program < Son, Father >();

现在我们令:iobj1= iobj2,可行吗?

            乍一看似乎可行——为什么呢?因为联想到右边的两个子类Son会被自动隐式转化成其父类Father。就好像是Father f = new Son()一样可以(注意:我们把子类隐式转化成父类成为“协变”,反之成为“反变”)。而且,根据接口定义,输入方向是接受一个Son(实际存储在iobj2中,被隐式转成Father),输出的时候还是存储的Son被隐式转化成Father输出。

            这种思考逻辑固然没有错,但是它有一个前提条件——即从iobj1输入方向看,必须是Son到Father,输出的话也必须是Son到Father!但是泛型仅仅是一个定义,谁能够保证在类中Father或者Son一定是输入(或者是输出)参数呢?如果我们改变成以下的形式呢?

class Program<T,V>:InAndOut<T,V>

    {

        private object value = default(T);

        public V Output()

        {

            return (V) value;

        }

        public void Input(T tv)

        {

            value = tv;

        }

    }

            这样就出现了问题——首先,iobj1指向iobj2(接受一个Father的参数,此时如果我的Father输入的是Son2,那么实际转化到Son的时候就发生异常了;同样地,输出因为是Son2不能转化成Son,因此发生异常)。

这个问题就是因为输入输出方向不明确所导致的。如果我们强制是一开始就给出的输入(输出方向)。即V只能作为输入,T只能作为输出就可以了。

推而广之,假设存在两个泛型TV,假设VTVT的子类)。那么泛型接口之间转换的一般规则是:输出类型是父类,“输出”一般是协变;反之,输入类型是子类,一般是反变。约束这种输入输出泛型的规则就是:输出线的接口加关键词out,输入加in如下所示:

Interface  InAndOut<out T, in V>

             {

             void Input(V value);

            T Output();

}

            那么你输入以下代码,就可以输出结果:

InAndOut<Father, Son> iobj1 = new Program < Father, Son >();

InAndOut<Son, Father> iobj2 = new Program < Son, Father >();

            这种规则本质上是编译器的行为理解。但是不一定就是正确结果,考察下列例子:

InAndOut<Father, Son> iobj1 = new Program < Father, Son >();

InAndOut<Son2, Father> iobj2 = new Program < Son2, Father >();

这一段代码照样可以通过编译,但是运行仍旧报异常。为什么呢?因为“输入端”和“输出端”尽管都符合了隐式转换的条件,但是你注意:把一个Son对象存储到iobj2的时候,iboj2的输出要求是Son2,而不是Son! 因此要保证运行正确,必须做到这样:

InAndOut<Father, Son> iobj1 = new Program < Father, Son >();

InAndOut<Son, Father> iobj2 = new Program < Son, Father >();

输入端接受Son,能够隐式转成蓝色的Father,蓝色Father存储的子类对象同样必须可以转化成Son(即一个协变的东西必须能够支持其反变;反之,一个反变的泛型必须支持其协变泛型,这就是著名的“协变反变类型”,简称“协-反变类型”)。

证明:(红色的Son开始):隐式转化成Father(协变),然后蓝色的Father(其中存储Son)强制转化成绿色的Son(反变)。

同理,(黑色的Father开始):黑色Father返回的内容(存储Son)可以强制转化成蓝色Father的内容(反变),同时可以隐式转化成绿色Son(协变)。我觉得可以使用对角线规则验证(猜想,对于任意的泛型A,B,C,D):

            InAndOut<A,B>

                                                (B既可以转成D,也可以转成C;输出A包含的内容

                                                   可以转化成D,也可以转化成C)

            InAndOut<C, D>