C#学习笔记(五):泛型

认识泛型

泛型使类型参数化,从而实现了算法上的代码重用。

同时由于去掉了转换中装箱和拆箱的操作,使用泛型还可以提高程序的运行速度。

我们先看看C#自带的使用了泛型的类:

 1 using System.Collections.Generic;
 2 
 3 namespace Study
 4 {
 5     class Program
 6     {
 7         static void Main(string[] args)
 8         {
 9             List<int> list1 = new List<int>();
10             list1.Add(100);
11             int i = list1[0];
12 
13             List<string> list2 = new List<string>();
14             list2.Add("Hello");
15             string s = list2[0];
16         }
17     }
18 }

通过使用泛型,我们可以重复利用List提供的功能,而不用每个类型对应去写一个List的类。

泛型在类上的实现

下面我们自己使用泛型编写一个简单的类,如下:

 1 using System;
 2 
 3 namespace Study
 4 {
 5     class Program
 6     {
 7         static void Main(string[] args)
 8         {
 9             Test<int> test1 = new Test<int>();
10             test1.myValue = 100;
11             Console.WriteLine(test1.myValue);
12 
13             Test<string> test2 = new Test<string>();
14             test2.myValue = "Hello";
15             Console.WriteLine(test2.myValue);
16         }
17     }
18 
19     public class Test<T>
20     {
21         private T _myValue;
22 
23         public T myValue
24         {
25             set { _myValue = value; }
26             get { return _myValue; }
27         }
28     }
29 }

Test类中的尖括号里面的T即为泛型,其可以表示任意的类型。

泛型约束

我们上面示例中的T可以使用任意的类型,那么如果我们只希望T是某类型或某类型的子类该怎么办呢?

public class Test<T> where T : IComparable

如果这样写,则表示T必须是实现了IComparable接口的对象。

多个类型的情况

多个类型的写法如下:

public class Test<T, K> where T : IComparable where K : ICloneable

如上所示,一个类型如果要添加约束就需要写一个where进行对应,所以有多个就会有多个where关键字出现。

创建类型的情况

如果需要使用new创建一个类型,则需要在约束里添加new()的字符串,如下:

 1 using System;
 2 
 3 namespace Study
 4 {
 5     class Program
 6     {
 7         static void Main(string[] args)
 8         {
 9             Test<Data> test1 = new Test<Data>();
10             Console.WriteLine(test1.myComparable.s);
11 
12             Console.Read();
13         }
14     }
15 
16     public class Test<T> where T : IComparable, new()
17     {
18         private T _myComparable;
19 
20         public T myComparable
21         {
22             set { _myComparable = value; }
23             get { return _myComparable; }
24         }
25 
26         public Test()
27         {
28             _myComparable = new T();
29         }
30     }
31 
32     public class Data : IComparable
33     {
34         public string s = "Hello World!";
35 
36         public int CompareTo(object obj)
37         {
38             return 0;
39         }
40     }
41 }

但是如果是值类型,则不需要这么写,但是要约束T为值类型,如下:

 1 using System;
 2 
 3 namespace Study
 4 {
 5     class Program
 6     {
 7         static void Main(string[] args)
 8         {
 9             Test<int> test1 = new Test<int>();
10             Console.WriteLine(test1.myComparable);
11 
12             Console.Read();
13         }
14     }
15 
16     public class Test<T> where T : struct
17     {
18         private T _myComparable;
19 
20         public T myComparable
21         {
22             set { _myComparable = value; }
23             get { return _myComparable; }
24         }
25 
26         public Test()
27         {
28             _myComparable = new T();
29         }
30     }
31 }

default关键字

当我们需要对泛型T置空时不能直接写“xxx=null;”因为只有当 T 为引用类型时,语句 t = null 才有效;只有当 T 为数值类型而不是结构时,语句 t = 0 才能正常使用。所以我们使用default关键字就可以解决这个问题,如下:

_myComparable = default(T);

泛型继承

子类也有相同的泛型时:

1 public class A<T>
2 { }
3 
4 public class B<T> : A<T>
5 { }

当然,你可以使用另外的名称,只要能对应上即可:

1 public class A<T>
2 { }
3 
4 public class B<K> : A<K>
5 { }

子类指定好类型:

1 public class A<T>
2 { }
3 
4 public class B : A<string>
5 { }

子类添加新类型:

1 public class A<T>
2 { }
3 
4 public class B<T, K> : A<T>
5 { }

泛型在方法上的实现

如果要在方法上添加类上没有指定的类型,可以直接在方法上添加泛型:

 1 using System;
 2 
 3 namespace Study
 4 {
 5     class Program
 6     {
 7         static void Main(string[] args)
 8         {
 9             Test<int> test = new Test<int>();
10             Console.WriteLine(test.Func<string>("Hello"));
11 
12             Console.Read();
13         }
14     }
15 
16     public class Test<T>
17     {
18         public K Func<K>(K k)
19         {
20             return k;
21         }
22     }
23 }

泛型在委托上的实现

委托上也可以使用泛型,定义方法和在方法上使用泛型一致,如下:

 1 using System;
 2 
 3 namespace Study
 4 {
 5     class Program
 6     {
 7         static void Main(string[] args)
 8         {
 9             new Test();
10 
11             Console.Read();
12         }
13     }
14 
15     public class Test
16     {
17         public delegate T add<T>(T a, T b);
18 
19         public Test()
20         {
21             add<int> func1 = AddInt;
22             Console.WriteLine(func1(100, 23));
23 
24             add<float> func2 = AddFloat;
25             Console.WriteLine(func2(1.2f, 0.03f));
26         }
27 
28         private int AddInt(int a, int b)
29         {
30             return a + b;
31         }
32 
33         private float AddFloat(float a, float b)
34         {
35             return a + b;
36         }
37     }
38 }

泛型接口

泛型接口的使用和泛型类一致,大家可以查看微软自己的文档:https://msdn.microsoft.com/zh-cn/library/kwtft8ak(VS.80).aspx

泛型和静态字段与方法

泛型同样可以使用在静态字段和方法中,由于静态字段和方法在内存中始终只存在一个,所以当我们使用了泛型的时候,编译器会帮我们自动生成对应的方法。

泛型静态的使用和动态一致就跳过不说了。

类型推断

我们在调用泛型方法时可以省略泛型类型的书写,完全交由编译器根据我们的类型来进行判断,这样可以减小代码量同时也更清晰:

 1 using System;
 2 
 3 namespace Study
 4 {
 5     class Program
 6     {
 7         static void Main(string[] args)
 8         {
 9             //没有类型推断
10             Console.WriteLine(CompareTo<int>(100, 100));
11 
12             //使用类型推断
13             Console.WriteLine(CompareTo(12.3f, 12.33f));
14             Console.WriteLine(CompareTo('a', 'a'));
15 
16             Console.Read();
17         }
18 
19         private static int CompareTo<T>(T a, T b) where T : IComparable
20         {
21             return a.CompareTo(b);
22         }
23     }
24 }

泛型的可变性

我们先看一个例子:

 1 using System;
 2 using System.Collections.Generic;
 3 
 4 namespace Study
 5 {
 6     class Program
 7     {
 8         static void Main(string[] args)
 9         {
10             List<B> listB = new List<B>();
11 
12             //下面这句代码会报错
13             //Cannot convert source type 'System.Collections.Generic.List<Study.B>' to 
14             //target type 'System.Collections.Generic.List<Study.A>'
15             List<A> listA = listB;
16 
17             Console.Read();
18         }
19     }
20 
21     public class A
22     {}
23 
24     public class B : A
25     {}
26 }

我们发现虽然B继承于A,但是List<A>和List<B>之间是不能相互转换的。

为了解决这个问题,微软在C#4.0中添加了对泛型的可变性的支持。

协变性

协变性指的是泛型类型参数可以从一个派生类隐式地转换为其基类。

out关键字

协变使用out关键字标识,如下:

public interface MyInterface<out T>

示例:

 1 using System;
 2 using System.Collections.Generic;
 3 
 4 namespace Study
 5 {
 6     class Program
 7     {
 8         static void Main(string[] args)
 9         {
10             MyInterface<B> myB = new MyClass<B>();
11             MyInterface<A> myA = myB;
12 
13             Console.Read();
14         }
15     }
16 
17     public interface MyInterface<out T>
18     {}
19 
20     public class MyClass<T> : MyInterface<T>
21     {}
22 
23     public class A
24     {}
25 
26     public class B : A
27     {}
28 }

逆变性

逆变性指的是泛型类型参数可以从一个基类隐式的转换为其派生类。

in关键字

public interface MyInterface<in T>

示例:

 1 using System;
 2 using System.Collections.Generic;
 3 
 4 namespace Study
 5 {
 6     class Program
 7     {
 8         static void Main(string[] args)
 9         {
10             MyInterface<A> myA = new MyClass<A>();
11             MyInterface<B> myB = myA;
12 
13             Console.Read();
14         }
15     }
16 
17     public interface MyInterface<in T>
18     {}
19 
20     public class MyClass<T> : MyInterface<T>
21     {}
22 
23     public class A
24     {}
25 
26     public class B : A
27     {}
28 }

注意事项

  1. 只有接口和委托支持协变和逆变,类或方法都不支持协变和逆变;
  2. 协变和逆变只支持引用类型,值类型不支持协变和逆变;
  3. 必须显示的使用out或in来标记协变和逆变;
  4. 委托的协变和逆变不要在多播委托中使用;
  5. 协变和逆变不能同时使用,只能选择一种;
原文地址:https://www.cnblogs.com/hammerc/p/4608349.html