C#2.0中主要的语法新特性
注:本文所有代码均来自MSDN,笔者仅仅是在里面添加部分注释。
1.泛型
(1)基本概念和用法
所谓泛型,就是以类型为参数。一般是类库设计者使用泛型来设计一些通用类型(特别是集合类),而类库使用者(或称客户端代码)在使用类库的时候传入实际的类型。所以,客户端代码的类型是编译期被定下来的,比起那些纯的通用类型(如.net中的ArrayList类),不仅提高了效率(无论是装箱、拆箱,还是强制类型转换,都是需要资源的),而且增加了编译期类型检查的好处。下面两段代码体现了这种区别:
System.Collections.ArrayList list = new System.Collections.ArrayList();
// 向链表中加入一个整型变量。这里会进行装箱的操作。
list.Add(3);
// 这里加入了一个字符串。即使我们本来是想生成一个整型数构成的链表,这里也不会报编译错误。
list.Add("It is raining in Redmond.");
int t = 0;
// 会抛InvalidCastException异常
foreach (int x in list)
{
t += x;
}
/*代码段2,使用了泛型的通用类型的应用*/
// 使用一个泛型类,并在客户端代码中指定了实际类型。
List<int> list1 = new List<int>();
// 这里既没有装箱,也没有类型转换的操作
list1.Add(3);
// 如果试图执行下面的代码,编译期就会报错
// list1.Add("It is raining in Redmond.");
在使用泛型的时候,泛型列表应紧跟泛型的修饰类型(class,struct,delegate,method,interface等),不管是类库还是客户端代码都是如此:
public interface ISessionChannel<TSession> { /*...*/ }
public delegate TOutput Converter<TInput, TOutput>(TInput from);
public class List<T> { /*...*/ }
(2)对类型参数的约束(即泛型应用中where关键字的作用)
如果我们不对泛型类型参数进行约束,那它在被客户端代码使用的时候,可以是任意一个clr能识别的类型。但很多时候,作为类库开发者,我们希望对类型本身作出限制,比如说我们希望这个类型是某个类型的子类,或者说我们希望它是一个值类型。这个时候我们就要用到where关键字了,如下:
public class GenericList<T> where T : CustomClass{}
CustomClass是一个我们自定义的引用类型,这里就约束了T必须为CustomClass的子类。
关于约束,还要注意一点就是它是可以叠加的,也就是说,你可以多个不互斥约束加到某一个类型身上:
class Stack<T> where T : System.IComparable<T>, IEnumerable<T>{}
(3)其他
和泛型有关的其他一些重要概念包括:
I.泛型和反射的关系:因为公共语言运行库 (CLR) 能够在运行时访问泛型类型信息,所以可以使用反射获取关于泛型类型的信息,方法与用于非泛型类型的方法相同。
II.泛型委托的使用
III.运行库中的泛型:什么时候使用泛型类型的专用版本
这里就不一一赘述了。
2.迭代器
可能我们很多人都用过迭代器(如果您用过foreach关键字,那您就用过用迭代器实现的类库),但可能没有自己设计过迭代器(设计一个类,让类的使用者可能通过foreach关键字来遍历类中的数据集合)。下面是从MSDN上摘下的一个简单的使用迭代器的例子:
public class SampleCollection
{
public int[] items;
public SampleCollection()
{
items = new int[5] { 5, 4, 7, 9, 3 };
}
public System.Collections.IEnumerable BuildCollection() //必须是符合IEnumerable的返回类型
{
for (int i = 0; i < items.Length; i++)
{
yield return items[i]; //注意这里的yeild关键字
}
}
}
class MainClass
{
static void Main()
{
SampleCollection col = new SampleCollection();
// Display the collection items:
System.Console.WriteLine("Values in the collection are:");
foreach (int i in col.BuildCollection()) //既然BuildCollection返回的是一个迭代器,就可以对他使用foreach关键字
{
System.Console.Write(i + " ");
}
}
}
而BuildCollection()方法正是返回这样一个迭代器:
BuildCollection() cil managed
{
// 代码大小 21 (0x15)
.maxstack 2
.locals init (class flier.SampleCollection/'<BuildCollection>d__0' V_0,
class [mscorlib]System.Collections.IEnumerable V_1)
IL_0000: ldc.i4.s -2
//在这里构造了一个迭代器类型
IL_0002: newobj instance void flier.SampleCollection/'<BuildCollection>d__0'::.ctor(int32)
IL_0007: stloc.0
IL_0008: ldloc.0
IL_0009: ldarg.0
IL_000a: stfld class flier.SampleCollection flier.SampleCollection/'<BuildCollection>d__0'::'<>4__this'
IL_000f: ldloc.0
IL_0010: stloc.1
IL_0011: br.s IL_0013
IL_0013: ldloc.1
IL_0014: ret
} // end of method SampleCollection::BuildCollection
而上面编译器所做的一切工作,都是由yield关键字指定的。
3.可空类型
可空类型允许我们为布尔、整数等值类型附上Null值,这在有些编程环境下会非常有用(例如,数据库中的布尔型字段可以存储值 true 或 false,或者,该字段也可以未定义)。下面是MSDN上的一个关于可空类型的小例子:
{
static void Main()
{
int? num = null; //T?是System.Nullable<T>的简写,所以这里等同于 Nullable<int> num = null;
if (num.HasValue == true)
{
System.Console.WriteLine("num = " + num.Value);
}
else
{
System.Console.WriteLine("num = Null");
}
int y = num.GetValueOrDefault();// 通过GetValueOrDefault()方法返回一个可空类型的默认值
int z = num??99; // ??是一个特殊的运算符,帮助定义默认值。如果num==null,则z=99;否则z=num.
// 在使用它的Value属性前,应该用HasValue进行判断,否则可能会出异常。
try
{
y = num.Value;
}
catch (System.InvalidOperationException e)
{
System.Console.WriteLine(e.Message);
}
}
}
4.匿名方法
它的概念和用法都比较简单,就是把一个代码块直接传递给一个委托的语法形式,如下例所示:
delegate void Printer(string s);
class TestClass
{
static void Main()
{
// Instatiate the delegate type using an anonymous method:
Printer p = delegate(string j)
{
System.Console.WriteLine(j);
};
// Results from the anonymous delegate call:
p("The delegate using the anonymous method is called.");
// The delegate instantiation using a named method "DoWork":
p = new Printer(TestClass.DoWork);
// Results from the old style delegate call:
p("The delegate using the named method is called.");
}
// The method associated with the named delegate:
static void DoWork(string k)
{
System.Console.WriteLine(k);
}
}
5.名字空间别名限定符
这个限定符的用法为"A::B",其中左边A是一个名字空间的别名,B是一个当前要使用的类型名称,整个合在一起就是指定去A下面查找B。很显然,这个限定符用于多个名字空间下定义了相同类型名的情况。其中A有一个系统已经定义的别名global,指代全局名字空间。下面是一个例子:
class TestApp
{
// Define a new class called 'System' to cause problems.
public class System { }
// Define a constant called 'Console' to cause more problems.
const int Console = 7;
const int number = 66;
static void Main()
{
// Error Accesses TestApp.Console
//Console.WriteLine(number);
// Error Accesses TestApp.System
//System.Console.WriteLine(number);
// OK
global::System.Console.WriteLine(number);
}
}
当然,除了global,你也可以自己定义一些名字空间别名,这里就不多举例了。
6.委托中的类型相容性(协变与逆变)
这其实是一种让我们在一些合法隐式转换的帮助下,把返回值具有类属关系的代理和传入参数具有类属关系的代理进行合并的一种方法,前者被称为“协变”,后者被称为“逆变”。
(1)协变
{
}
class Dogs : Mammals
{
}
class Program
{
// 定义一个delegate.
// 这个代理的返回值是Mammals,但通过协变,它可适用于返回值可以向下转型成Mammals的类型,如返回值为Dogs的情况
public delegate Mammals HandlerMethod();
public static Mammals FirstHandler()
{
return null;
}
public static Dogs SecondHandler()
{
return null;
}
static void Main()
{
HandlerMethod handler1 = FirstHandler;
// 这样的做法在C#2.0级之后的版本中都是合法的
HandlerMethod handler2 = SecondHandler;
}
}
(2)逆变
public Form1()
{
InitializeComponent();
lastActivity = new System.DateTime();
this.textBox1.KeyDown += this.MultiHandler; //works with KeyEventArgs
this.button1.MouseClick += this.MultiHandler; //works with MouseEventArgs
}
// Event hander for any event with an EventArgs or
// derived class in the second parameter
private void MultiHandler(object sender, System.EventArgs e)
{
lastActivity = System.DateTime.Now;
}