CSharpThinking协变和逆变(一)

实践是检验真理的捷径。

  本章主要是理解C#中的协变和逆变的关系,对今后理解泛型会有很大帮助。

1.协变与逆变的概念及代码说明。

 C#1:数组是强类型,强类型不允许内部数据不能相互转换。C#2中引入了对协变与逆变的限制,而C#4中又适当放宽了政策,不过这一切对数组没有任何影响,不过可以用一系列的接口和集合代替数组。

 1             Stephen[] MySelf = new Stephen[1];
 2             MySelf[0] = new Stephen() { Name = "name1", Name2 = "name2" };
 3             StephenBase[] MyBase = MySelf; // 协变
 4             Stephen[] OtherMe = (Stephen[])MyBase; // 逆变
 5 
 6 ...
 7 
 8         public class StephenBase
 9         {
10             public StephenBase() { }
11             public string Name { get; set; }
12         }
13 
14         public class Stephen : StephenBase
15         {
16             public Stephen()
17             {
18 
19             }
20 
21             public string Name2 { get; set; }
22         }

委托的协变和逆变

逆变:其中void KeyPressEventHandler(object sender, KeyPressEventArgs e) 继承自 void EventHandler(object sender, EventArgs e)

1 void LogPlainEvent(object sender ,EventArgs e)
2 {
3      Console.WriteLine("An event occurred!");
4 }
5 
6 Button btn = new Button();
7 btn.Click += LogPlainEvent; // 委托逆变
8 btn.KeyPress += LogPlainEvent; // 委托逆变

协变:

 1            delegate Stream StreamFactory();
 2 
 3             StreamFactory sf = GenerateMeoryStream; // 委托协变
 4             using (Stream s = sf())
 5             {
 6                 int data;
 7                 while ((data = s.ReadByte()) != -1)
 8                 {
 9                     Console.WriteLine(data);
10                 }
11             } 
12 
13         static MemoryStream GenerateMeoryStream()
14         {
15             byte[] buffer = new byte[16];
16             for (int i = 0; i < buffer.Length; i++)
17             {
18                 buffer[i] = (byte)i;
19             }
20             return new MemoryStream(buffer);
21         }

注:协变和逆变是指从基类转换成子类或从子类转换到基类。

2.协变与逆变转换时,本质上没有改变对象,只是编译器如何看待这些值

1             Stephen[] MySelf = new Stephen[1];
2             MySelf[0] = new Stephen() { Name = "name1", Name2 = "name2" }; 
3             StephenBase[] MyBase = MySelf; // 协变
4             Stephen[] OtherMe = (Stephen[])MyBase; // 逆变
5 
6             Console.WriteLine("ChildName: " + OtherMe[0].Name2);
7             Console.ReadLine();

 接口继承也会在二次复原转换后依然保留原始信息。

 1         public static void InterfaceTest()
 2         {
 3             HelloStephen stephen = new HelloStephen()
 4             {
 5                 Email = @"Balabala@happy.com",
 6                 Oicq = @"10001",
 7                 IsAlive = true
 8             };
 9 
10             IHelloStephen istephen = (IHelloStephen)stephen;
11             HelloStephen targetStephen = HelloStephen.FindStephen(istephen);
12 
13             Console.WriteLine("Is Stephen alive ?");
14             Console.WriteLine(targetStephen.IsAlive);
15             Console.WriteLine("Contact Me ? To : " + targetStephen.Email + " Or QQ : " + targetStephen.Oicq);
16         }
 1     /// <summary>
 2     /// Descrption:
 3     /// IStephen Interface
 4     /// </summary>
 5     public interface IHelloStephen
 6     {
 7         string Email { get; set; }
 8         string Oicq { get; set; }
 9         bool IsAlive { get; set; }
10     }
11 
12     public class HelloStephen : IHelloStephen
13     {
14         #region IHelloStephen
15         string _Email;
16         string _Oicq;
17         bool _isAlive;
18 
19         public bool IsAlive
20         {
21             get { return _isAlive; }
22             set { _isAlive = value; }
23         }
24         public string Email
25         {
26             get { return _Email; }
27             set { _Email = value; }
28         }
29         public string Oicq
30         {
31             get { return _Oicq; }
32             set { _Oicq = value; }
33         }
34         #endregion
35 
36         #region HelloStephen
37         public string MyCNBlogs { get; set; }
38         #endregion
39 
40         #region Method
41         public static HelloStephen FindStephen(IHelloStephen stephen)
42         {
43             return (HelloStephen)stephen ?? new HelloStephen() { IsAlive = false };
44         }
45         #endregion
46     }

  

3.方法组转换。

  C#1中如果要创建一个委托实例,就必须同时指定委托类型和要采取的操作;C#2提供从方法组(一个方法名,表达式)到一个兼容类型的隐式转换。

1 // C#1
2 Thread t = new Thread(new ThreadStart(MyMethod));
3 
4 // C#2
5 Thread t = new Thread(MyMethod);
View Code

4.C#1与C#2中委托协变逆变的不同之处。

   存在不兼容的风险:

1 EventHandler general = new EventHandler(Event);
2 KeyPressEventHandler key = new KeyPressEventHandler (general); // C#1报错,而C#2不报错。

C#1要求两个委托的类型签名必须匹配。

 本文多数实例均引自《C# in Depth Second Edition》

原文地址:https://www.cnblogs.com/cuiyansong/p/3102974.html