您的位置:首页 > 编程语言 > C#

C# 2.0 新特性

2011-03-29 11:23 204 查看
C# 2.0引入了几项语言扩展,其中包括分部类型 (Partial Type) 、可空类型 (Nullable Type)泛型 (Generic)、匿名方法 (Anonymous Method)、迭代器 (Iterator)。
 

分部类型浅显易懂,看看相关文档就能理解,这里重点谈谈泛型。

一、泛型的定义。

微软文档这样定义泛型:泛型是这样的一些类、结构、接口和方法,它们为其存储或使用的一个或多个类型提供占位符。

一个简单的泛型例子:public class List<T>

其中:T就是System.Collections.Generic.List<T>的实例所存储类型的占位符,当定义泛型类的实例时,必须指定这个实例所存储的实际类型,

:List<string> ls=new List<string>();

泛型允许程序员将一个实际的数据类型的规约延迟至泛型的实例被创建时才确定。

二、C#2.0为什么要推出泛型?

先看一个简单的例子:

下面是一个简单的 Stack 类,它将数据存储在一个 object 数组中,它的两个方法 Push 和 Pop 分别使用 object接受和返回数据:
public class Stack
{
readonly int m_Size;
int m_StackPointer = 0;
object[] m_Items;
public Stack():this(100)
{}
public Stack(int size)
{
m_Size = size;
m_Items = new object[m_Size];
}
public void Push(object item)
{
if(m_StackPointer >= m_Size)
throw new StackOverflowException();
m_Items[m_StackPointer] = item;
m_StackPointer++;
}
public object Pop()
{
m_StackPointer--;
if(m_StackPointer >= 0)
{
return m_Items[m_StackPointer];
}
else
{
m_StackPointer = 0;
throw new InvalidOperationException(
"Cannot pop an empty stack");
}
}
}

虽然使用类型 object 使得 Stack 类非常灵活,但这种方式仍存在某些缺陷。例如,我们可以将任何类型的值(例如一个 Customer 实例)推入堆栈。但是,当从堆栈中检索某个值时,必须将 Pop 方法的结果显式强制转换回相应的类型,这样的代码编写起来颇为繁琐,而且在运行时执行的类型检查会造成额外的开销从而影响性能:
Stack stack = new Stack();
stack.Push(new Customer());
Customer c = (Customer)stack.Pop();
再比如,当我们将一个值类型(例如 int)的值传递给 Push 方法时,则该值将自动被装箱。当以后检索该 int 时,必须使用显式类型强制转换将其取消装箱:
Stack stack = new Stack();
stack.Push(3);
int i = (int)stack.Pop();
这样的装箱和取消装箱操作由于涉及动态内存分配和运行时类型检查而额外增加了性能开销。
上述 Stack 类还有一个潜在的问题就是我们无法对放到堆栈上的数据的种类施加限制。实际上,可能会发生这种情况:将一个 Customer 实例推入堆栈,而在检索到该实例之后却意外地将它强制转换为错误的类型:
Stack stack = new Stack();
stack.Push(new Customer());
string s = (string)stack.Pop();
虽然上面的代码错误使用了 Stack 类,但是从技术角度讲该代码可以视作是正确的,编译器不会报告编译时错误。这个问题在该代码被执行之前不会暴露出来,但在执行该代码时会引发 InvalidCastException。
显然,如果能够指定元素类型,Stack 类将能够从中受益。有了泛型,我们便可以做到这一点。泛型为使用c#语言编写面向对象程序增加了极大的效力和灵活性。不会强行对值类型进行装箱和拆箱,或对引用类型进行向下强制类型转换,所以性能得到提高。泛型为开发者提供了一种高性能的编程方式,能够提高代码的重用性,并允许开发者编写非常优雅的解决方案。
 
下面的示例声明一个带有类型形参 T 的泛型 Stack 类。类型形参在 < 和 > 分隔符中指定并放置在类名后。Stack<T> 的实例的类型由创建时所指定的类型确定,该实例将存储该类型的数据而不进行数据类型转换。这有别于同 object 之间的相互转换。类型形参 T 只起占位符的作用,直到在使用时为其指定了实际类型。注意,这里的 T 用作内部项数组的元素类型、传递给 Push 方法的参数类型和 Pop 方法的返回类型:
public class Stack<T>
{
T[] items;
int count;
public void Push(T item) {...}
public T Pop() {...}
}
在使用泛型类 Stack<T> 时,将指定用于替换 T 的实际类型。在下面的示例中,指定了 int 作为 T 的类型实参 (type argument)
Stack<int> stack = new Stack<int>();
stack.Push(3);
int x = stack.Pop();
Stack<int> 类型称为构造类型 (constructed type)。在 Stack<int> 类型中,出现的每个 T 都被替换为类型实参 int。在创建 Stack<int> 的实例后,items 数组的本机存储是 int[] 而不是 object[]。无疑,这比非泛型的 Stack提供了更高的存储效率。同样,Stack<int> 的 Push 和 Pop 方法所操作的也是 int 类型的值。如果将其他类型的值推入堆栈则产生编译时错误。而且在检索值时也不再需要将它们显式强制转换为原始类型。
泛型提供了强类型机制,这意味着如果将一个 int 值推入 Customer 对象的堆栈将导致错误。正如 Stack<int> 仅限于操作 int 值一样,Stack<Customer> 仅限于操作 Customer 对象,编译器将对下面示例中的最后两行报告错误:
Stack<Customer> stack = new Stack<Customer>();
stack.Push(new Customer());
Customer c = stack.Pop();
stack.Push(3);                   // Type mismatch error
int x = stack.Pop();             // Type mismatch error
泛型类型声明可以含有任意数目的类型形参。上面的 Stack<T> 示例只有一个类型形参,而一个泛型 Dictionary 类可能具有两个类型形参,一个用于键的类型,一个用于值的类型:
public class Dictionary<K,V>
{
public void Add(K key, V value) {...}
public V this[K key] {...}
}
在使用上述 Dictionary<K,V> 时,必须提供两个类型实参:
Dictionary<string,Customer> dict = new Dictionary<string,Customer>();
dict.Add("Peter", new Customer());
Customer c = dict["Peter"];

三、泛型的应用。

 •轻量级的结构中使用泛型

public struct Point<T>
{
public T X;
public T Y;
}
Point<int> point;
point.X = 1;
point.Y = 2;
Point<double> point;
point.X = 1.2;
point.Y = 3.4;
Default关键字
public T Pop()
{
m_StackPointer--;
if(m_StackPointer >= 0)
{
return m_Items[m_StackPointer];
}
else
{
m_StackPointer = 0;
throw new InvalidOperationException(
"Cannot pop an empty stack");
}
}

default关键字
• 假设您不希望在堆栈为空时引发异常,而是希望返回堆栈中存储的类型的默认值
– 值类型返回0(整型、枚举和结构)
– 引用类型返回null
• 如果是基于object,则可以简单的返回null
public T Pop()
{
m_StackPointer--;
if(m_StackPointer >= 0)
{
return m_Items[m_StackPointer];
}
else
{
m_StackPointer = 0;
return default(T);
}
}

多个泛型
•单个类型可以定义多个泛型
class Node<K,T>
{
public K Key;
public T Item;
public Node<K,T> NextNode;
public Node()
{
Key = default(K);
Item = defualt(T);
NextNode = null;
}
public Node(K key,T item,Node<K,T> nextNode)
{
Key = key;
Item = item;
NextNode = nextNode;
}
}
public class LinkedList<K,T>
{
Node<K,T> m_Head;
public LinkedList()
{
m_Head = new Node<K,T>();
}
public void AddHead(K key,T item)
{
Node<K,T> newNode = new Node<K,T>
(key,item,m_Head.NextNode);
m_Head.NextNode = newNode;
}
}

泛型别名
using List = LinkedList<int,string>;
class ListClient
{
static void Main(string[] args)
{
List list = new List();
list.AddHead(123,"AAA");
}
}
• 在文件头部使用using为特定类型取别名
• 别名作用范围是整个文件
泛型约束-概述
•为什么要泛型约束?
•先看以下示例:
public class LinkedList<K,T>
{
T Find(K key)
{...}
public T this[K key]
{
get{return Find(key);}
}
}
LinkedList<int,string> list = new
LinkedList<int,string>();
list.AddHead(123,"AAA");
list.AddHead(456,"BBB");
string item = list[456];
Debug.Assert(item == "BBB");

• Find方法实现
T Find(K key)
{
Node<K,T> current = m_Head;
while(current.NextNode != null)
{
if(current.Key == key) //Will not compile
break;
else
current = current.NextNode;
}
return current.Item;
}
因为编译器不知道K(实例化时的实际类型)是否支持== 运算符。例
如,默认情况下,结构不提供这样的实现。
public interface IComparable
{
int CompareTo(object obj);
}
if(current.Key.CompareTo(key) == 0)
if(((IComparable)(current.Key)).CompareTo(key) == 0)

泛型约束-派生约束
• Find方法的解决方法
– 泛型增加一个派生约束(Derivation Constraints)
public class LinkedList<K,T> where K : IComparable
{
T Find(K key)
{
Node<K,T> current = m_Head;
while(current.NextNode != null)
{
if(current.Key.CompareTo(key) == 0)
break;
else
current = current.NextNode;
}
return current.Item;
}
//Rest of the implementation
}
1.Where关键字
2.K:IComparable表示K只接受实现IComparable接口的
类型。尽管如此,还是无法避免传入值类型的K所带来的装箱问题。
System.Collections.Generic 命名空间定义了泛型接口
IComparable<T>:
public interface IComparable<T>
{
int CompareTo(T other);
bool Equals(T other);
}
泛型约束-派生约束
• 在C#2.0中,所有的派生约束必须放在类的实际派生列表之后,如:
• 通常,只须在需要的级别定义约束。比如:在Node节点定义
IComparable<K>约束是没有意义的。如果一定要在Node上定义
IComparable<K>约束,则LinkedList上也要定义此约束
public class LinkedList<K,T> : IEnumerable<T> where K : IComparable<K>
{...}
class Node<K,T> where K : IComparable<K>
{...}
public class LinkedList<KeyType,DataType>
where KeyType : IComparable<KeyType>
{
Node<KeyType,DataType> m_Head;
}

迭代器是 C# 2.0 中的新功能。迭代器是方法、get 访问器或运算符,它使您能够在结构中支持 foreach 迭代,而不必实现整个 IEnumerable 接口。您只需提供一个迭代器,即可遍历类中的数据结构。当编译器检测到迭代器时,它将自动生成 IEnumerableIEnumerable<T> 接口的 Current、MoveNext 和 Dispose 方法。

迭代器概述

迭代器是可以返回相同类型的值的有序序列的一段代码。

迭代器可用作方法、运算符或 get 访问器的代码体。

迭代器代码使用 yield return 语句依次返回每个元素。yield break 将终止迭代。

可以在类中实现多个迭代器。每个迭代器都必须像任何类成员一样有唯一的名称,并且可以在 foreach 语句中被客户端代码调用,如下所示:foreach(int x in SampleClass.Iterator2){}

迭代器的返回类型必须为 IEnumerableIEnumeratorIEnumerable<T> 或 IEnumerator<T>。

yield 关键字用于指定返回的值。到达 yield return 语句时,会保存当前位置。下次调用迭代器时将从此位置重新开始执行。

迭代器对集合类特别有用,它提供一种简单的方法来迭代不常用的数据结构(如二进制树)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息