思考
如果想要實現下面泛型類別的程式碼,該怎麼做呢?
1
2
3
4
5
6
| class AGenericClass<T>
{
public void Say(T t) {
t.Hello();
}
}
|
t.Hello()意味著我傳進來的一定是一個class,而這個class一定要有Hello()這個方法。
那麼,該用什麼要規範?用interface,讓它實現IHello介面。
1
2
3
4
| interface IHello
{
void Hello();
}
|
泛型類加上約束 where T
特化T的類別,只要實現IHello介面就行。
那泛型類要怎麼加上約束呢?換句話說,我們要怎麼去描述這個T,能夠讓它表示一個必須繼承了IHello這個介面類別呢?
加上where T: IHello(這段就是泛型的約束),T這樣一個類型必須要繼承自IHello這樣的介面。
1
2
3
4
5
6
7
8
9
10
11
12
13
| //where T: IHello 這段就是泛型的約束
class AGenericClass<T> where T: IHello
{
public void Say(T t) {
//在外界傳進來的這個替代類型T,一定是繼承自IHello這樣的方法的
t.Hello();
}
}
interface IHello
{
void Hello();
}
|
泛型約束
- 泛型約束:對泛型中傳入的類型進行「校驗」,規定其必須滿足的條件
格式
在泛型類<T>後面加上where T: 條件
1
2
| public class AGenericClass<T> where T: 條件
{......}
|
舉例(用class來表示引用類型)
where T: class後面加的是class,class代表T它必須是「引用類型」
1
2
3
| //指T必須是引用類型
public class AGenericClass<T> where T: class
{......}
|
正確用法
string 是引用類型,所以它可以用來去特化當前泛型類。
1
2
| //正確用法,因為string是引用類型
AGenericClass<string> g = new AGenericClass<string>();
|
錯誤用法
因為int不是引用類型,int是值類型value type。
1
2
| //錯誤用法,因為int不是引用類型,int是值類型value type
AGenericClass<int> g = new AGenericClass<int>();//報錯
|
泛型約束分類
剛提到了兩個:
- 第一個是說咱們的
T必須繼承於某個介面interface。 where T: IHello - 第二個是說咱們的
T必須是一個「引用類型」。where T: class
現在來一一列舉:
1
2
3
4
5
6
7
| 泛型約束分類:
1. class: 泛型T必須是引用類型
2. struct: 泛型T必須是值類型
3. new():泛型T必須是包含一個無參構造方法的class,如果同時有其他約束,new()必須放在最後
4. 類名(Person):泛型T要馬就是Person類,或是Person的子類/孫子類
5. 介面約束:泛型T必須實現一個或多個介面
6. 多佔位符約束: 使用where關鍵字對多個佔位符進行各自約束
|
1
| public class AGenericClass<T> where T: class {...}
|
1
| public class AGenericClass<T> where T: struct {...}
|
new約束:泛型必須包含無參構造方法
如與其他約束共用,則放在最後
1
2
| public class AGenericClass1<T> where T: new() {...}
public class AGenericClass2<T> where T: class, new() {...}
|
- 類名約束:泛型必須是某類或派生自某類 與介面共用,則放在介面前方
1
2
3
| //T必須是Person,或是Person的子類
public class AGenericClass<T> where T: Person {...}
public class AGenericClass<T> where T: Person, IHello {...}
|
1
| public class AGenericClass<T> where T: IOne, ITwo, IThree {...}
|
- 多泛型約束 泛型類可以有兩個佔位符,我們也可以對這兩個佔位符,各自做出各自的約束
1
| class AGenericClass<T, B> where T : IOne where B : class { }
|
1. class:泛型T必須是引用類型
用class來表示引用類型
1
2
3
4
5
6
7
8
9
| //1. class: 引用類型
class AGenericClassForClass<T> where T : class { }
static void Main(string[] args)
{
//1.class表示引用類型約束,int是值類型,所以不行
//AGenericClassForClass<int> g = new AGenericClassForClass<int>();
AGenericClassForClass<string> g = new AGenericClassForClass<string>();
}
|
2. struct:泛型T必須是值類型
值類型約束struct:泛型必須是值類型
1
2
3
4
5
6
7
8
9
| //2. struct: 值類型
class AGenericClassForValueType<T> where T : struct { }
static void Main(string[] args)
{
//2.struct表示值類型約束,string是引用類型,所以不行
//AGenericClassForValueType<string> g = new AGenericClassForValueType<string>();
AGenericClassForValueType<float> g = new AGenericClassForValueType<float>();
}
|
3. new():當前的T,必須包含無參構造方法
new():泛型T必須是包含一個無參構造方法的class,如果同時有其他約束,new()必須放在最後
1
2
3
4
5
6
7
8
9
| //3. new(): 當前的T,必須包含無參構造方法
class TestForNew { }
class AGenericClassForNew<T> where T : new() { }
static void Main(string[] args)
{
//3.new()表示T必須包含無參構造方法
AGenericClassForNew<TestForNew> g3 = new AGenericClassForNew<TestForNew>();
}
|
class TestForNew { }系統會自動帶一個無參的構造方法,如果手動加一個有參的構造方法,那系統就不會為這個一個類去生成一個無參的構造方法,就會報錯。
1
2
3
4
5
6
7
8
9
10
11
| //如果手動加一個有參的構造方法,那系統就不會為這個一個類去生成一個無參的構造方法,就會報錯
class TestForNew {
public TestForNew(int x) { }
}
class AGenericClassForNew<T> where T : new() { }
static void Main(string[] args)
{
//3.new()表示T必須包含無參構造方法
AGenericClassForNew<TestForNew> g3 = new AGenericClassForNew<TestForNew>();
}
|
那怎麼辦呢?再手動加上一個無參的構造方法就可以了。
1
2
3
4
5
6
7
8
9
10
11
| class TestForNew {
public TestForNew() { } //無參構造方法
public TestForNew(int x) { } //有參構造方法
}
class AGenericClassForNew<T> where T : new() { }
static void Main(string[] args)
{
//3.new()表示T必須包含無參構造方法
AGenericClassForNew<TestForNew> g3 = new AGenericClassForNew<TestForNew>();
}
|
如果同時有其他約束,new()必須放在最後
1
2
| class AGenericClassForNew<T> where T : class, new()
{ }
|
4. 類名(Person):泛型T要馬就是Person類,或是Person的子類/孫子類
如果與介面同時存在,則放在介面的前面
1
2
3
4
5
6
7
8
9
10
11
12
13
| //4. 類名: T必須是本類,或是其子類/孫子類...
class Person { } //父類
class Teacher : Person { } //子類
class EnglishTeacher : Teacher { } //孫子類
class AGenericClassForClassName<T> where T : Person { }
static void Main(string[] args)
{
//4.類名約束:T必須是當前類,或是子類/孫子類
AGenericClassForClassName<Person> g41 = new AGenericClassForClassName<Person>();
AGenericClassForClassName<Teacher> g42 = new AGenericClassForClassName<Teacher>();
AGenericClassForClassName<EnglishTeacher> g43 = new AGenericClassForClassName<EnglishTeacher>();
}
|
5. 介面約束: T必須實現一個或者多個介面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| //5. 介面約束: T必須實現一個或者多個介面
interface IFireble {
void fire();
}
interface IRunnable {
void Run();
}
class Tank : IFireble, IRunnable
{
public void fire() { }
public void Run() { }
}
class AGenericClassForInterface<T> where T : IFireble,IRunnable { }
static void Main(string[] args)
{
//5.介面約束:T必須實現一個或多個介面
AGenericClassForInterface<Tank> g5 = new AGenericClassForInterface<Tank>();
}
|
類名約束+介面約束
再結合第四點: 類名約束(如果與介面同時存在,則放在介面的前面)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| interface IFireble {
void fire();
}
interface IRunnable {
void Run();
}
class Machine { }
class Tank : Machine, IFireble, IRunnable
{
public void fire() { }
public void Run() { }
}
//類名約束:如果與介面同時存在,則放在介面的前面
class AGenericClassForInterface<T> where T : Machine, IFireble, IRunnable { }
static void Main(string[] args)
{
//5.介面約束:T必須實現一個或多個介面
AGenericClassForInterface<Tank> g5 = new AGenericClassForInterface<Tank>();
}
|
Tank class符合三個條件:
- 它是
Machine的子類 - 它繼承了
IFireble介面 - 它繼承了兩個介面
6. 多佔位符約束: 使用where關鍵字對多個佔位符進行各自約束
1
2
3
4
5
6
7
8
9
10
| //6. 多類型佔位符約束
//T:必須是一個引用類型(class)、必須有一個無參構造方法(new())
//B:必須實現IFireble介面
class AGenericClassForMulti<T, B> where T : class, new() where B : IFireble, IRunnable { }
static void Main(string[] args)
{
//6.多佔位符約束
AGenericClassForMulti<TestForNew, Tank> g6 = new AGenericClassForMulti<TestForNew, Tank>();
}
|
完整程式碼
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
| /* 泛型約束分類
* 1. class: 泛型T必須是引用類型
* 2. struct: 泛型T必須是值類型
* 3. new():泛型T必須是包含一個無參構造方法的class,如果同時有其他約束,new()必須放在最後
* 4. 類名(Person):泛型T要馬就是Person類,或是Person的子類/孫子類
* 5. 介面約束:泛型T必須實現一個或多個介面
* 6. 多佔位符約束: 使用where關鍵字對多個佔位符進行各自約束
*/
namespace HelloWorld
{
//1. class: 引用類型
class AGenericClassForClass<T> where T : class { }
//2. struct: 值類型
class AGenericClassForValueType<T> where T : struct { }
//3. new(): 當前的T,必須包含無參構造方法
class TestForNew {
public TestForNew() { } //無參構造方法
public TestForNew(int x) { } //有參構造方法
}
class AGenericClassForNew<T> where T : class, new() { }
//4. 類名: T必須是本類,或是其子類/孫子類...
class Person { } //父類
class Teacher : Person { } //子類
class EnglishTeacher : Teacher { } //孫子類
class AGenericClassForClassName<T> where T : Person { }
//5. 介面約束: T必須實現一個或者多個介面
interface IFireble {
void fire();
}
interface IRunnable {
void Run();
}
class Machine { }
class Tank : Machine, IFireble, IRunnable
{
public void fire() { }
public void Run() { }
}
class AGenericClassForInterface<T> where T : Machine, IFireble, IRunnable { }
//6. 多類型佔位符約束
//T:必須是一個引用類型(class)、必須有一個無參構造方法(new())
//B:必須實現IFireble介面
class AGenericClassForMulti<T, B> where T : class, new() where B : IFireble, IRunnable { }
internal class Program
{
static void Main(string[] args)
{
//1.class表示引用類型約束,int是值類型,所以不行
//AGenericClassForClass<int> g = new AGenericClassForClass<int>();
AGenericClassForClass<string> g1 = new AGenericClassForClass<string>();
//2.struct表示值類型約束,string是引用類型,所以不行
//AGenericClassForValueType<string> g = new AGenericClassForValueType<string>();
AGenericClassForValueType<float> g2 = new AGenericClassForValueType<float>();
//3.new()表示T必須包含無參構造方法
AGenericClassForNew<TestForNew> g3 = new AGenericClassForNew<TestForNew>();
//4.類名約束:T必須是當前類,或是子類/孫子類
AGenericClassForClassName<Person> g41 = new AGenericClassForClassName<Person>();
AGenericClassForClassName<Teacher> g42 = new AGenericClassForClassName<Teacher>();
AGenericClassForClassName<EnglishTeacher> g43 = new AGenericClassForClassName<EnglishTeacher>();
//5.介面約束:T必須實現一個或多個介面
AGenericClassForInterface<Tank> g5 = new AGenericClassForInterface<Tank>();
//6.多佔位符約束
AGenericClassForMulti<TestForNew, Tank> g6 = new AGenericClassForMulti<TestForNew, Tank>();
}
}
}
|
泛型約束與繼承
- 挪有泛型約束的類作為父類,則其子類可以選擇特化父類,或者 使用與父類同樣/更嚴格的約束
例如我們有一個父類MyClass、一個子類MyClassSon繼承MyClass
1
2
| class MyClass {...} //父類
class MyClassSon: MyClass {...} //子類
|
現在有一個泛型類AGenericClass,它的T類型必須要滿足是一個MyClass這樣的類別,
1
| class AGenericClass<T> where T: MyClass {...}
|
特化父類
第一種特化方式
使用MyClass來特化父類
1
| public class Test: AGenericClass<MyClass> {...}
|
第二種特化方式
使用MyClassSon來特化父類
1
| public class Test: AGenericClass<MyClassSon> {...}
|
與父類相同約束
1
2
| public class Test: AGenericClass<B> where B: MyClass
{...}
|
更嚴格約束
1
2
| public class Test: AGenericClass<B> where B: MyClassSon
{...}
|
https://www.bilibili.com/video/BV1kP41197YV/