c#2.0新特性
范型
我们知道通用的数据结构可以采用object存储任何数据类型。使用object问题是:
- 显示的强制转带来的代码复杂性
- 换装箱拆箱的性能损失(为什么有性能损失?因为涉及动态内存分配和运行时类型检查)。还有一些运行时才会出现的类型转换异常也是我们难以在代码编写的时候能够检查到的,防不胜防。
范型应时而生,它的思路是什么呢?它接受带有类型参数并存储这个类型而不转换它,类型参数在类名字后的<T>中指定。T相当于一个占位符,直到使用的时候才指定一个实际的类型。确切的说当应用程序首次创建一个构造范型类型的实例时,.net公共语言运行时的实时编译器JIT将在进程中把范型IL和元数据转化为本地代码并把类型参数转化为实际的类型。对于这个泛型类型的后续引用将会使用相同的本机代码。这也就是传说中的范型类型实例化。
看一段代码:
范型的一个有趣话题:约束
问题溯源:由于T代表的可能是任何类型,所以我们使用T的方法仅限于Equals GetHasCode ToString,那么我们要使用某些特定数据类型的方法呢?比如实现了IComparable接口的数据类型的CompareTo方法?
一种方法是强制转换到IComparable接口。这种方法的缺点是:1.进行运行时动态类型检查增加了性能上的开销,2.自然地如果key没有实现IComparable接口的异常报告推迟到了运行时
另一种方法就是约束列表,关键词是where,后面跟的是类或者接口的列表,还有一个可选、特殊的new()约束;其实就是说这个类型必须要有一个公开无参构造函数,这就允许泛型类使用这种构造函数来创建实例。约束是一把双刃剑,一方面它提供了编译时类型检查,增强了性能,但是它也限制了范型类型的能力。
范型方法
范型方法在方法名字后面使用<>指定一个或者多个类型参数,类型参数可以出现在参数列表,返回类型和方法体内。编译器会使用一种类型推断的机制通过其他参数类推断正确的类型参数。
给出一些范型的例子:

Code
1
public class Test<T>
2
{
3
public T item
{ get; set; }
4
5
public void Display()
6
{
7
Console.WriteLine(item.ToString());
8
}
9
10
}
11
12
public class Map<K, V>
13
{
14
Dictionary<K, V> d = new Dictionary<K, V>();
15
public void Add(K key, V value)
16
{
17
18
d.Add(key, value);
19
}
20
public void Display()
21
{
22
foreach (var item in d.Keys)
23
{
24
Console.WriteLine("Key:" + item + " Value:" + d[item]);
25
}
26
}
27
}
28
29
public class ComparableTest<T> where T : IComparable
30
{
31
public T item
{ get; set; }
32
33
public void Display()
34
{
35
if (item.CompareTo(10) > 0)
36
{
37
Console.WriteLine(item.ToString() + ">" + "10");
38
}
39
else
40
{
41
Console.WriteLine(item.ToString() + "<" + "10");
42
}
43
}
44
45
}
46
47
public class MapWithConstraint<K, V> where V : new()
48
{
49
Dictionary<K, V> d = new Dictionary<K, V>();
50
public void Add(K key, V value)
51
{
52
53
d.Add(key, value);
54
}
55
public void Display()
56
{
57
foreach (var item in d.Keys)
58
{
59
Console.WriteLine("Key:" + item + " Value:" + d[item]);
60
}
61
}
62
}
63
64
public class GenericMethodTest
65
{
66
public T Display<T, V, K>(T t, K k, V v)
67
{
68
Console.WriteLine(t.ToString() + " " + k.ToString() + " " + v.ToString());
69
return t ;
70
}
71
}
72
public class Student
73
{
74
public int ID
{ get; set; }
75
public string Name
{ get; set; }
76
}
77
class Program
78
{
79
static void Main(string[] args)
80
{
81
Test<int> t = new Test<int>();
82
t.item = 10;
83
t.Display();
84
85
86
Map<string, int> map = new Map<string, int>();
87
map.Add("King", 23);
88
map.Add("XiaoQiang", 24);
89
map.Display();
90
91
ComparableTest<int > test = new ComparableTest<int >();
92
test.item = 123;
93
test.Display();
94
95
96
// ComparableTest<Student > test2 = new ComparableTest<Student >();
97
98
99
GenericMethodTest g = new GenericMethodTest();
100
Console.WriteLine(g.Display(23, 32, 52));
101
102
103
104
Console.ReadLine();
105
106
}
107
}
108
匿名方法
匿名方法其实就是体现了这样一个原则:如无必要,勿增实体;我们在一个简单的WinForm环境中来说明这个问题:一个按钮单击事件我们可以这样来定义响应代码。
1
this.button1.Click += delegate
2
{
3
this.label1.Text = "King2002";
4
};
5
6
this.button1.Click += delegate(object sender, System.EventArgs arg)
7
{
8
this.label1.Text = "Test";
9
};
10
this.button1.Click += new System.EventHandler(this.button1_Click);
11
private void button1_Click(object sender, EventArgs e)
12
{
13
MessageBox.Show("Hello");
14
}
15
16
17
匿名方法使得代码对于委托的实现更加简单,匿名方法还有一个用途就是操作一些私有成员,因为它相当于共享了一部分代码。
这里会有一个疑问:匿名方法和委托类型的隐式转换有什么要求?答案:只要参数列表和委托类型的返回值是兼容的就可以完成转换。
- 参数列表兼容:无参数或者参数的数量、类型、修饰符严格匹配
- 无返回类型,所有return语句形管的表达式可以被隐式转换到委托的类型
后面我们会看到更简单使用delegate的例子。
迭代器
一个对象如果是可枚举的,那么我们可以使用foreach语句来遍历其中的元素。实际上是调用了这个对象的GetEnumberator方法,它返回一个enumberator(枚举器)。实现枚举器很难但是我们可以使用迭代器实现!
1
public class DaysOfTheWeek : System.Collections.IEnumerable
2
{
3
string[] m_Days =
{ "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat" };
4
5
public System.Collections.IEnumerator GetEnumerator()
6
{
7
for (int i = 0; i < m_Days.Length; i++)
8
{
9
yield return m_Days[i];
10
}
11
}
12
}
13
14
class TestDaysOfTheWeek
15
{
16
static void Main()
17
{
18
// Create an instance of the collection class
19
DaysOfTheWeek week = new DaysOfTheWeek();
20
21
// Iterate with foreach
22
foreach (string day in week)
23
{
24
System.Console.Write(day + " ");
25
}
26
}
27
}
28
上面是MSDN上的一个简单的例子,有了直观的印象我们可以深究一点:
迭代器是产生值的有序序列的一个语句块,迭代器不是一种成员,它只是实现函数成员的方式。Yield return语句产生迭代的下一个值 yield break 语句指明迭代已经完成;GetEnumerator返回值只要是枚举器接口或者是可枚举接口(System.Collections.IEnumerable System.Collections.IEnumerator System.Collections.Generic.IEnumerable<T> System.Collections.Generic.IEnumerator<T> ),迭代器就可以被用做函数体。
不完整类型
不完整类型完全是为了更好的进行代码管理。仔细观察我们现在添加一个页面时,它的后代代码就使用了不完整类型:
public partial class _Default : System.Web.UI.Page
c#3.0新特性