一、概述
泛型并不是C#语音一开始就带有的特性,而是在FCL2.0之后实现的新功能。
泛型是将类型参数化,以便于更大范围的进行代码复用。
二、泛型使用范围及用法
1.总是优先考虑泛型
以List基本实现为例:
public class MyList<T> { T[] items; public T this[int i] { get { return items[i]; } set { this.items[i] = value; } } public int Count { get { return this.items.Length; } } }
我们可以把T理解成一个占位符,在C#泛型编译生产IL(中间代码)中,T就是一个占位符的角色。在运行时,JIT(即时编辑器)会用实际的代码中输入的T类型替换T。
这里很多匠友会有疑问,为什么不用Object类型代替T?
原因有二:
其一Object无法支持类型安全。
其二效率消耗,从Object类型转换成其他类型会带来装箱和拆箱的性能损耗。
2. 尽量避免在泛型类型中声明Static成员:
static void Main(string[] args) { MyList<int> list1 = new MyList<int>(); MyList<int> list2 = new MyList<int>(); MyList<string> list3 = new MyList<string>(); Console.WriteLine(MyList<int>.Count); Console.WriteLine(MyList<string>.Count); Console.Read(); } public class MyList<T> { public static int Count { get; set; } public MyList() { Count++; } }
代码输出:
2
1
实际上你为T指定不同的数据类型,他们之间是不共享静态成员的。
注意:非泛型类中的泛型方法并不会在运行时的本地代码中生产不同的类型。
3. 泛型约束
这里的“约束”并非是限制参数,正好相反,“约束”让泛型参数具有更多的行为和属性。
public class Salary { public int BaseSalary { get; set; } public int Bonus { get; set; } } public class SalaryCompare { public int Compare<T>( T t1,T t2) where T : Salary { if (t1.BaseSalary>t2.BaseSalary) { return 1; } else if (t1.BaseSalary == t2.BaseSalary) { return 0; } else { return -1; } } }
以下是泛型指定那些约束:值类型,引用类型,无参公共构造方法等。
4. 泛型类型变量指定初始值
public T Func<T>() { //T t = null;//编译报警 //T t = 0;//编译报警 T t = default(T); return t; }
5. FCL(框架类库)中的委托声明
Action表示接受0个或者多个输入参数,无返回值。
Func表示接受0个或者多个输入参数,带返回值。
predicate表示定义一组条件并判断参数是否符合条件。
static void Main(string[] args) { Func<int, int, int> add = Add; Action<string> print = Print; print(add(2, 5).ToString()); Console.Read(); } static int Add(int i, int j) { return i + j; } static void Print(string msg) { Console.WriteLine(msg); }
我们应该习惯在代码中使用这类委托代替自己写的委托声明。
6. 使用lambda表达式代替方法和匿名方法
这里我们就上一例代码重构说明:
static void Main(string[] args) { Func<int, int, int> add = (i, j) => { return i + j; }; Action<string> print = (msg) => { Console.WriteLine(msg); }; print(add(2, 5).ToString()); Console.Read(); }
Lambda表达式操作符“=>”的左侧是方法参数,右侧是方法体,其本质是匿名方法。我们应该在实际的编码中熟练运用Lambda表达式,避免写出繁琐而又不美观的代码。
7.小心闭包中的陷阱
static void Main(string[] args) { List<Action> lists = new List<Action>(); for (int i = 0; i < 5; i++) { Action t = () => { Console.WriteLine(i.ToString()); }; lists.Add(t); } foreach (Action t in lists) { t(); } Console.Read(); }
我们设计的意图是让匿名方法接收参数i,并输出:
0
1
2
3
4
而实际输出:
5
5
5
5
5
通过IL查看器可以模拟出如下代码结果:
class Program { static void Main(string[] args) { List<Action> lists = new List<Action>(); TempClass tempclass = new TempClass(); for ( tempclass.i = 0; tempclass.i < 5; tempclass.i++) { Action t = tempclass.TempFunc; lists.Add(t); } foreach (Action t in lists) { t(); } Console.Read(); } } class TempClass { public int i; public void TempFunc() { Console.WriteLine(i.ToString()); } }
这样一来,即使代码执行后离开了原局部变量i的作用域,包含该闭包对象的作用域也还存在。
要实现预期结果可以将壁报对象放在i的作用域内:
static void Main(string[] args) { List<Action> lists = new List<Action>(); for (int i = 0; i < 5; i++) { int tmp = i ; Action t = () => { Console.WriteLine(i.ToString()); }; lists.Add(t); } foreach (Action t in lists) { t(); } Console.Read(); }
实际输出:
0
1
2
3
4
总结:
泛型的优点是多方面的,无论是泛型类还是泛型方法都同时具备可重用性、类型安全和高效率等特性,这都是非泛型类和非泛型方法无法具备的。
在实际的编程过程中尽量使用最少最高效的代码实现功能,这样不仅让你的代码可读性强也会看起来赏心悦目,好的代码是会说话的,就算你不去注释其他匠友也会读懂它。
最近正在学习敏捷,希望沿着这条路继续走下去,坚持、坚持、、