# 泛型

# 泛型的定义

上一章讲的集合类,是没有类型化的,使用时需要把object项转换为实际的对象类型,因为所有的对象都继承自System.Object,所以任何对象都可以存储在ArrayList中,在操作时对一些类的操作可能并不适用于另一些类,导致崩溃,这时就想更好的办法是使用强类型化的集合类,这些类派生于CollectionBase,可以拥有自己的方法,来添加,删除和访问集合的成员,但它可能把集合成员限制为派生于某种类型的类,或必须支持某个接口,这样导致在定义集合中的新类时,必须符合下面的情况:

  • 使用某个集合类,该类已经定义为可以包含新类型的项。
  • 创建一个新的集合类,它包含新类型的项,实现所有需要的方法。

一般情况下,新的类型需要额外的功能,常常需要用到新的集合类,因此创建集合类会花费大量时间,在这样的情况下,泛型类便大大简化了这个问题,泛型类是以实例化过程中提供的类型为基础建立的,可以方便地对对象进行强类型化。泛型不只适合集合,但集合特别适用于泛型,它们大都在System.Collection.Generic名称空间,泛型并不限于类,还可以创建泛型接口,泛型方法,泛型委托。

注意在C++中,编译器可以检测出在哪里使用了模板的某个特定类型,在C#中所有的操作都在运行期间进行。它并不是被编译为许多类型,才能进行实例化,在.NET运行库允许在需要时动态生成泛型类,在实例化之前,不会存在该泛型类。

# 使用泛型

System.Collection.Generic名称空间中有两个泛型集合类型,它们是List<T>和Dictionary<K,V>,一个是T类型对象的集合,一个是KV键值对类型的集合。

# List

List<T>泛型集合类型更便于使用,不用像前面那样从CollectionBase派生一个类,然后实现需要的方法,被需要的方法已经实现了。

对泛型列表进行排序与对其他列表进行排序是一样的,前面讲了使用IComparer和IComparable接口比较两个对象,然后对该类型对象的集合进行排序,现在可以使用泛型接口IComparer<T>和IComparable<T>:

泛型方法 非泛型方法
int IComparable<T>.CompareTo(T otherObj) int IComparable.CompareTo(object otherObj)
bool IComparable<T>.Equals(T otherObj) 不存在,使用继承的other.Equals()
int IComparer<T>.Compare(T objectA,T objectB) int IComparer.Compare(object objectA,object objectB)
bool IComparer<T>.Equals(T objectA,T objectB) 不存在,使用继承的object.Equals()
int IComparer<T>.GetHashCode(T objectA) 不存在,使用继承的object.GetHashCode()

除了上面的方法,还可以提供泛型委托,作为排序方法,有下面两个排序方法:

  • Comparison<T>:这个委托用于排序方法,其返回类型和参数为:

    int method(T objectA,T objectB)

  • Predicate<T>:这个委托类型用于搜索方法,其返回类型和参数为:

    bool method(T targetObject)

下面看实例:

class Book
{
    public string bookName;
    public int id;
    public Book(string name,int idT)
    {
        bookName = name;
        id = idT;
    }
    public override string ToString()
    {
        return "书名:" + bookName+"   ;ID为:"+id;
    }
}

class Library : List<Book>
{
    public Library()
    {

    }
    public Library(IEnumerable<Book> initItems)
    {
        foreach (var one in initItems) this.Add(one);
    }
    public void ShowBook()
    {
        foreach(var one in this)
        {
            Console.WriteLine(one);
        }
    }
}

class BookDelegate
{
    public static int CompareBook(Book b1,Book b2)
    {
        if (b1.id != b2.id)
        {
            return b1.id - b2.id;
        }
        else
        {
            return b1.bookName.CompareTo(b2.bookName);
        }
    }

    public static bool GetPartBook(Book book)
    {
        if(book.id < 5 && book.id >= 2)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

}

class Program
{
    static void Main(string[] args)
    {
        Library lib = new Library();
        lib.Add(new Book("历史",3));
        lib.Add(new Book("数学",1));
        lib.Add(new Book("英语",3));
        lib.Add(new Book("语文",4));
        lib.Add(new Book("政治",5));
        lib.Add(new Book("地理",2));
        lib.Add(new Book("化学",2));

        Comparison<Book> sorter = new Comparison<Book>(BookDelegate.CompareBook);
        lib.Sort(sorter);
        //lib.Sort(BookDelegate.CompareBook);
        lib.ShowBook();

        Console.WriteLine("-----------------");
        Predicate<Book> searcher = new Predicate<Book>(BookDelegate.GetPartBook);
        Library lib2 = new Library(lib.FindAll(searcher));
        //Library lib2 = new Library(lib.FindAll(BookDelegate.GetPartBook));
        lib2.ShowBook();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

注意上面的注释的部分会隐式创建Comparison<Book>和Predicate<Book>。

# Dictionary

Dictionary<K,V>类型可定义键/值对的集合,这个类需要实例化两个类型,分别用于键和值,以表示集合中的各项。

static void Main(string[] args)
{
    Dictionary<string, int> student = new Dictionary<string, int>()
    {
        ["liming1"] = 29,
        ["liming2"] = 25,
        ["zhangsan"] = 24,
        ["wangwu"] =26,
        ["zhenliu"] =28,
    };

    foreach(var one in student.Keys)
    {
        Console.WriteLine(one);
    }
    foreach(var one in student.Values)
    {
        Console.WriteLine(one);
    }
    Dictionary<string, int> stu2 = new Dictionary<string, int>(StringComparer.CurrentCultureIgnoreCase);
    stu2.Add("Liming", 1);
    stu2.Add("LiMing", 1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 定义泛型

# 定义泛型类

上面介绍了使用泛型,现在我们让自定义类支持泛型。

class MyGClass<T1, T2, T3>
{
    private T1 innerObj;
    private T2 innerObj2;
    public MyGClass(T1 item)
    {
        //innerObj = T1();
        //不能假定T1是什么类型,有可能只是值类型,那就没有构造函数。
        //只能假设T1继承自System.Object或可以封装到System.Object中。
        innerObj = item;
        innerObj2 = default(T2);
        Console.WriteLine(innerObj2);
    }
    public T1 GetObj
    {
        get { return innerObj; }
    }
    public void GetALLType()
    {
        Console.WriteLine( "T1=" + typeof(T1).ToString() + ";T2=" + typeof(T2).ToString() + ";T3=" + typeof(T3).ToString());
    }
    public bool IfNull(T1 t1Obj,T2 t2Obj)
    {
        if (t1Obj == null)
        {
            Console.WriteLine("param1 is null!");
        }
        //if (t1Obj == t2Obj)
        //不能比较两个泛型对象。
        return false;
    }
}

static void Main(string[] args)
{
    MyGClass<int, string, char> MyObj = new MyGClass<int, string, char>(2);
    MyObj.GetALLType();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

注意:

  • default关键字可以根据泛型类型给变量设置默认值。
  • 不能假定泛型参数是类类型或值类型,只能假定它是继承System.Object的类型。
# 泛型约束

上面的泛型被称为无绑定(unbounded)类型,没有对它们进行任何约束,通过约束(constraining)类型,可以限制用于实例化泛型类的类型,比如限制该泛型参数为继承自某个类型的类类型。

下面是一些可用约束:

约束 定义
struct 类型必须是值类型
class 类型必须是引用类型
baseClassName 类型必须是基类或继承自基类
interfaceName 类型必须是接口或者实现了该接口
new() 类型必须有一个公共的无参构造函数

来看实例:

class MyClass1
{
}

class MyClass2:MyClass1,MyInterace
{
}

class MyClass3 : MyClass2
{
}

interface MyInterace
{

}

class MyGClass<T1> where T1 : struct
{
}

class MyGClass2<T1> where T1 : class
{

}

class MyGClass3<T1> where T1:class,new()
{

}

class MyGClass4<T1,T2,T3> where T1:MyClass2 where T2 : T1 where T3:MyInterace
{

}

static void Main(string[] args)
{
    MyGClass<int> MyGObj = new MyGClass<int>();
    MyGClass2<MyClass1> MyGObj2 = new MyGClass2<MyClass1>();
    MyGClass3<MyClass2> MyGObj3 = new MyGClass3<MyClass2>();
    //MyGClass3<MyClass2> MyGObj3 = new MyGClass3<MyClass3>();
    MyGClass4<MyClass3, MyClass3,MyClass3> MyGObj4 = new MyGClass4<MyClass3, MyClass3,MyClass3>();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

注意:

  • 如果new()用作约束,它必须是为类型指定的最后一个约束。
  • 可以把一个泛型参数作为另一个泛型参数的约束,这称为裸类型约束(naked type constraint),但是不能循环依赖。
  • 约束必须在继承说明符的后面。

泛型约束在类继承中也是需要注意的,即便派生类的泛型参数是基类的泛型参数的超集也不行:

class MyClass1
{
}

class MyGClass2<T>where T:MyClass1
{
}

//class MyClass3 <T> :MyGClass2<T> where T:class
class MyGClass3 <T> :MyGClass2<T> where T:MyClass1
{
    //打开的泛型类型
}

//关闭的泛型类型
class MyClass4 : MyGClass2<MyClass1>
{
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

在类中可以使用隐式转换运算符简化一些操作,比如:

class Book
{
    public string bookName;
    public int id;
    public Book(string name, int idT)
    {
        bookName = name;
        id = idT;
    }
    public override string ToString()
    {
        return "书名:" + bookName + "   ;ID为:" + id;
    }
}

class Library<T>:List<T> where T : Book
{
    public static implicit operator List<Book>(Library<T> lib)
    {
        List<Book> result = new List<Book>();
        foreach(var one in lib)
        {
            result.Add(one);
        }
        return result;
    }
    public static Library<T> operator +(Library<T> lib1,List<T> list)
    {
        Library<T> lib = new Library<T>();
        Console.WriteLine("-----------Use Library + List-----------");
        foreach(var one in lib1){
            lib.Add(one);
        }
        foreach(var one in list)
        {
            lib.Add(one);
        }
        return lib;
    }

    //public static Library<T> operator +(List<T> list, Library<T> lib) => list + lib;
    //运行时错误哦
    public static Library<T> operator +(List<T> list, Library<T> lib) => lib + list;
    public void ShowBook()
    {
        foreach(var one in this) {
            Console.WriteLine(one);
        }
    }

}


static void Main(string[] args)
{
    Library<Book> lib = new Library<Book>();
    lib.Add(new Book("历史",3));
    lib.Add(new Book("数学",1));
    lib.Add(new Book("英语",3));

    List<Book> list = new List<Book>();
    list.Add(new Book("语文",4));
    list.Add(new Book("政治",5));
    list.Add(new Book("地理",2));
    list.Add(new Book("化学",2));

    Library<Book> lib2 = lib + list;
    lib2.ShowBook();
    lib2 = list + lib;
    lib2.ShowBook();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

# 其他泛型类型

下面是定义泛型结构体:

//泛型结构体
struct MyStruct<T1, T2>
{
    public T1 item1;
    public T2 item2;
}
class MyClass1
{

}
class MyClass2:MyClass1
{

}
//泛型接口
interface MyInter1<T> where T : MyClass1
{
    T GetClass();
}

class MyClass3 : MyInter1<MyClass2>
{
    public MyClass2 GetClass()
    {
        throw new NotImplementedException();
    }
}

//泛型方法
class MyClass4
{
    public T GetDefault<T>() => default(T);
}

class MyClass5<T>
{
    public T2 GetDefault<T2>() where T2:T => default(T2);
    public T GetDefault<T,T2>() where T2:T => default(T);
}

//泛型委托
public delegate T1 MyDelegate<T1, T2>(T2 op1, T2 op2) where T1 : T2;

static void Main(string[] args)
{
    MyStruct<int, string> myStruct = new MyStruct<int, string>();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# 变体

变体(variance)是协变(covariance)和抗变(contravariance)的统称,这两个概念由 .Net 4引入,在之前虽然有它们的身影但是比较难实现,因为它们需要定制的编译过程。

泛型协变的类型参数可以把派生类泛型接口引用放在基类型变量中,协变类型参数只能用作方法的返回值或属性get访问器:

public interface Interface1<out T>
{
}
class MyClass1:Interface1<MyClass2>
{
}
class MyClass2 : MyClass1
{
}

static void Main(string[] args)
{
    MyClass1 myObj1 = new MyClass1();
    MyClass2 myObj2 = new MyClass2();
    Interface1<MyClass2> myInterface2 = myObj2;
    Interface1<MyClass1> myInterface1 = myInterface2;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

而泛型抗变类型则是把基类接口引用放在派生类型的变量中,抗变类型参数只能用作方法参数,不能用作返回类型:

public interface Interface1<in T>
{
}
class MyClass1:Interface1<MyClass1>
{
}
class MyClass2 : MyClass1
{
}

static void Main(string[] args)
{
    MyClass1 myObj1 = new MyClass1();
    MyClass2 myObj2 = new MyClass2();
    Interface1<MyClass1> myInterface1 = myObj1;
    Interface1<MyClass2> myInterface2 = myInterface1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

比如List容器的Sort()方法可以使用它,我们自定义一个类,然后实现Compare方法。