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新特性
自动实现属性
Auto-Implemented Properties
1
public
class
Card
2
3
{
4
5 //这样的属性是不是简单多了?
6
7 public int ID { get; set; }
8
9 public string Password { get; set; }
10
11 }
12
13
class
Customer
14
15
{
16
17 public double TotalPurchases { get; set; }
18
19 public string Name { get; private set; } // read-only
20
21 public int CustomerID { get; private set; } // read-only
22
23 }
24
- 自动属性必须包含get set,如果是只读的就添加private关键字
- Attributes 不允许使用自动属性,这里还是推荐使用常规的属性书写方式
<ms-help://MS.MSDNQTR.v90.en/dv_csref/html/aa55fa97-ccec-431f-b5e9-5ac789fd32b7.htm>
检索表达式
Query Expressions
LINQ毫无疑问是c#3.0最抢眼的东西,关于LINQ的文章已经很多,这里不展开,详细资料请参考:
<ms-help://MS.MSDNQTR.v90.en/dv_csref/html/40638f19-fb46-4d26-a2d9-a383b48f5ed4.htm>
int
[] items
=
new
int
[]
{ 1, 2, 3, 4, 5 }
;
IEnumerable
<
int
>
ints
=
from item
in
items
where
item
>
2.5
select item;
foreach
(var p
in
ints)
{
Console.WriteLine(p);
}
隐式类型变量
Implicitly Typed Variables (var)
内部变量的类型可以使用var而不是确切的类型。var关键字可以指示编译器通过右侧的初始化部分来推断实际的数据类型。
var num
=
5
;
var a
=
new
[]
{ 2, 5, 6, 7 }
;
//
anon 被编译成匿名类型 注意下面的Name Age都没有定义
var anon
=
new
{ Name = "Terry", Age = 34 }
;
var list
=
new
List
<
int
>
();
using
(var file
=
new
System.IO.StreamReader(
@"
D:\http.txt
"
, Encoding.Default))
{
Console.WriteLine(file.ReadLine());
Console.WriteLine(anon.Name );
Console.WriteLine(anon.Age );
}
var的使用会有一些约束:<ms-help://MS.MSDNQTR.v90.en/dv_csref/html/b9218fb2-ef5d-4814-8a8e-2bc29b0bbc9b.htm>
对象、集合初始化
Object and Collection Initializers
设计完成之后生成了一些类代码,但是这里有一个问题就是属性代码的填写,在FX3.0属性代码的已经简化,
类的初始化也提供了更简单的书写方式;可以看一下下面代码段中c2的初始化,列表对象的初始化按照同样的格式也会大大的简化。
1
public
class
Card
2
3
{
4
5 //这样的属性是不是简单多了?
6
7 public int ID { get; set; }
8
9 public string Password { get; set; }
10
11 }
12
13
class
Program
14
15
{
16
17 static void Main(string[] args)
18
19 {
20
21 //通常我们初始化一个对象是这样做的
22
23 Card c = new Card();
24
25 c.ID = 2002;
26
27 c.Password = "henghenghaxi";
28
29
30
31 //现在我们可以这样做
32
33 Card c2 = new Card { ID = 23, Password = "testnum" };
34
35
36
37 Console.WriteLine(c.ID.ToString() + " - " + c.Password);
38
39
40
41 //列表的初始化
42
43 List<Card> cards = new List<Card>
44
45 {
46
47 new Card{ID=12,Password="12sfdgr"},
48
49 new Card{ID=13,Password="12sdfwsd"},
50
51 new Card{ID=14,Password="12jkhjh"},
52
53 new Card{ID=15,Password="1dfsed2"},
54
55 new Card{ID=16,Password="1sdfsd2"},
56
57
58
59 };
60
61 //看下面的例子
62
63 List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
64
65 List<int> digits2 = new List<int> { 0 + 1, 12 % 3, MakeInt() };
66
67
68
69 Console.WriteLine(cards.Count.ToString());
70
71 Console.ReadLine();
72
73 }
74
75 static int MakeInt()
76
77 {
78
79 return DateTime.Now.Second;
80
81 }
82
83
84
85 }
86
87
匿名类型
Anonymous Types
匿名类型把一系列的只读属性封装在一个对象里面,而并没有指定这个对象的类型编译器会给这个对象类型一个名字,但是这个名字在代码级别是不可用的。
看下面的代码:
// anon 被编译成匿名类型 注意下面的Name Age都没有定义
var anon = new { Name = "Terry", Age = 34 };
//anon.Age = 23;
最后一行如果执行编译器会给出一个错误:
Property or indexer 'AnonymousType#1.Age' cannot be assigned to -- it is read only
这个错误信息可以印证上面的说法。继续扩展上面的代码,一起看:
var card
=
from c3
in
cards
where
c3.ID
>
13
//
select c3;
select
new
{MyID= c3.ID,MyPassword= c3.Password }
;
foreach
(var item
in
card )
{
Console.WriteLine(item.MyID.ToString()+"---"+item.MyPassword );
}
这里的代码是匿名类型的一个典型应用,详细请查阅:
<ms-help://MS.MSDNQTR.v90.en/dv_csref/html/59c9d7a4-3b0e-475e-b620-0ab86c088e9b.htm>
扩展方法
Extension Methods
扩展方法是一个静态方法,可以关联在一种类型上,所以这个方法可以在他处调用。这样仿佛给某一个类型添加了方法!而实际上我们并没有改变原有的代码。
详情参阅:ms-help://MS.MSDNQTR.v90.en/dv_csref/html/175ce3ff-9bbf-4e64-8421-faeb81a0bb51.htm
public static class Test
{
public static string RemoveWhiteSpace(this string s)
{
return s.Replace(" ", "");
}
}
定义了上面的方法之后,我们可以在string类型上使用这个方法下面是在开发环境中的截图:
匿名方法
Anonymous Functions
1
delegate
void
Testdelegate(
string
s);
2
3
static
void
Show(
string
s)
4
5
{
6
7 Console.WriteLine(s);
8
9 }
10
11
//
1.1里面我们这样做
12
13
Testdelegate t
=
new
Testdelegate(Show);
14
15
16
17
//
2.0
18
19
Testdelegate t2
=
delegate
(
string
str)
{ Console.WriteLine(str); }
;
20
21
22
23
//
3.0
24
25
Testdelegate t3
=
(X)
=>
{ Console.WriteLine(X); }
;
26
27
t(
"
Kingtest1
"
);
28
29
t2(
"
20022396
"
);
30
31
t3(
"
20022458
"
);
32
在3.0里面我们的代码使用了Lambda表达式,详情参阅:
ms-help://MS.MSDNQTR.v90.en/dv_csref/html/57e3ba27-9a82-4067-aca7-5ca446b7bf93.htm
Lambada表达式在C#3.0中的典型应用:
ms-help://MS.MSDNQTR.v90.en/fxref_system.core/html/5a7a3466-7d99-dea6-a9fa-04043f951573.htm
//
Split the string into individual words.
string
[] words
=
sentence.Split(
'
'
);
//
Prepend each word to the beginning of the
//
new sentence to reverse the word order.
string
reversed
=
words.Aggregate((workingSentence, next)
=>
next
+
"
"
+
workingSentence);
bool
b
=
words.All(w
=>
w.Length
>
5
);
bool
b2
=
words.Any(w
=>
w.Length
>
10
);
一句话体会:
c#新特性的应用需要我们深入的学习以及在团队范围内的知识共享,这样才不致于让代码显得怪异。