1、泛型、
泛型概述
泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个新功能。泛型将类型参数的概念引入 .NET Framework,类型参数使得设计如下类和方法成为可能:这些类和方法将一个或多个类型的指定推迟到客户端代码声明并实例化该类或方法的时候。例如,通过使用泛型类型参数 T,您可以编写其他客户端代码能够使用的单个类,而不致引入运行时强制转换或装箱操作的成本或风险
-
使用泛型类型可以最大限度地重用代码、保护类型的安全以及提高性能。
-
泛型最常见的用途是创建集合类。
-
.NET Framework 类库在 System.Collections.Generic 命名空间中包含几个新的泛型集合类。应尽可能地使用这些类来代替普通的类,如 System.Collections 命名空间中的 ArrayList。
-
您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
-
可以对泛型类进行约束以访问特定数据类型的方法。
-
关于泛型数据类型中使用的类型的信息可在运行时通过反射获取。
泛型优缺点
1、编译时就可以保证类型安全2、不用做类型装换(装箱、拆箱),获得一定的性能提升
更多内容参考:
1、泛型使用示例
2、迭代器(iterator)
迭代器概述
迭代器是 C# 2.0 中的新功能。迭代器是方法、get 访问器或运算符,它使您能够在类或结构中支持foreach 迭代,而不必实现整个 IEnumerable 接口。您只需提供一个迭代器,即可遍历类中的数据结构。当编译器检测到迭代器时,它将自动生成 IEnumerable 或IEnumerable<T> 接口的Current、MoveNext 和 Dispose 方法
-
迭代器是可以返回相同类型的值的有序序列的一段代码。
-
迭代器可用作方法、运算符或 get 访问器的代码体。
-
迭代器代码使用 yield return 语句依次返回每个元素。yield break 将终止迭代。有关更多信息,请参见yield。
-
可以在类中实现多个迭代器。每个迭代器都必须像任何类成员一样有唯一的名称,并且可以在 foreach 语句中被客户端代码调用,如下所示:foreach(int x in SampleClass.Iterator2){}
-
迭代器的返回类型必须为 IEnumerable、IEnumerator、IEnumerable<T> 或IEnumerator<T>。
迭代器对集合类特别有用,它提供一种简单的方法来迭代不常用的数据结构(如二进制树)。
如下示例中,DaysOfTheWeek 类是将一周中的各天作为字符串进行存储的简单集合类。foreach 循环每迭代一次,都返回集合中的下一个字符串。
public class DaysOfTheWeek : System.Collections.IEnumerable
{
string[] m_Days = { "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat" };
public System.Collections.IEnumerator GetEnumerator()
{
for (int i = 0; i < m_Days.Length; i++)
{
yield return m_Days[i];
}
}
}
class TestDaysOfTheWeek
{
static void Main()
{
// Create an instance of the collection class
DaysOfTheWeek week = new DaysOfTheWeek();
// Iterate with foreach
foreach (string day in week)
{
System.Console.Write(day + " ");
}
}
}
更多内容参考: 迭代器(C# 编程指南)
3、可空类型(Nullable Type)
概述
-
可空类型表示可被赋值为 null 值的值类型变量。无法创建基于引用类型的可空类型。(引用类型已支持 null 值。)。
-
语法 T? 是 System.Nullable<T> 的简写,此处的 T 为值类型。这两种形式可以互换。
-
为可空类型赋值与为一般值类型赋值的方法相同,如 int? x = 10; 或 double? d = 4.108;。
-
如果基础类型的值为 null,请使用 System.Nullable.GetValueOrDefault 属性返回该基础类型所赋的值或默认值,例如 int j = x.GetValueOrDefault();
-
请使用 HasValue 和 Value 只读属性测试是否为空和检索值,例如 if(x.HasValue) j = x.Value;
-
如果此变量包含值,则 HasValue 属性返回 True;或者,如果此变量的值为空,则返回 False。
-
如果已赋值,则 Value 属性返回该值,否则将引发 System.InvalidOperationException。
-
可空类型变量的默认值将 HasValue 设置为 false。未定义 Value。
-
-
使用 ?? 运算符分配默认值,当前值为空的可空类型被赋值给非空类型时将应用该默认值,如 int? x = null; int y = x ?? -1;。
-
不允许使用嵌套的可空类型。将不编译下面一行:Nullable<Nullable<int>> n;
static void Main()
{
int? num = null;
if (num.HasValue == true)
{
System.Console.WriteLine("num = " + num.Value);
}
else
{
System.Console.WriteLine("num = Null");
}
//y is set to zero
int y = num.GetValueOrDefault();
// num.Value throws an InvalidOperationException if num.HasValue is false
try
{
y = num.Value;
}
catch (System.InvalidOperationException e)
{
System.Console.WriteLine(e.Message);
}
}
以上将显示输出:
num = Null
Nullable object must have a value.
4、匿名方法(Anonymous Method)
在C#2.0之前,只能用一个已经申明好的方法去创建一个委托。有了匿名方法后,可以在创建委托的时候直接传一个代码块过去。如下:
// Create a handler for a click event
button1.Click += delegate(System.Object o, System.EventArgs e)
{ System.Windows.Forms.MessageBox.Show("Click!"); };
或者
// Create a delegate instance
delegate void Del(int x);
// Instantiate the delegate using an anonymous method
Del d = delegate(int k) { /* ... */ };
下面的示例演示实例化委托的两种方法:
-
使委托与匿名方法关联。
-
使委托与命名方法 (DoWork) 关联。
// Declare a delegate
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);
}
}
C# 1.0的写法:
ThreadStart ts1 = new ThreadStart(Method1);
C# 2.0可以这么写
ThreadStart ts2 = Method1;
5、分布类(Partial)。
概述
可以将类、结构或接口的定义拆分到两个或多个源文件中。每个源文件包含类定义的一部分,编译应用程序时将把所有部分组合起来。在以下几种情况下需要拆分类定义:
-
处理大型项目时,使一个类分布于多个独立文件中可以让多位程序员同时对该类进行处理。
-
使用自动生成的源时,无需重新创建源文件便可将代码添加到类中。Visual Studio 在创建 Windows 窗体、Web 服务包装代码等时都使用此方法。您无需编辑 Visual Studio 所创建的文件,便可创建使用这些类的代码。
-
若要拆分类定义,请使用 partial 关键字修饰符
- 分部类型定义允许将单个类型(比如某个类)拆分为多个文件。Visual Studio 设计器使用此功能将它生成的代码与用户代码分离
6、静态类(static class)
静态类和类成员用于创建无需创建类的实例就能够访问的数据和函数。静态类成员可用于分离独立于任何对象标识的数据和行为:无论对象发生什么更改,这些数据和函数都不会随之变化。当类中没有依赖对象标识的数据或行为时,就可以使用静态类。
类可以声明为 static 的,以指示它仅包含静态成员。不能使用 new 关键字创建静态类的实例。静态类在加载包含该类的程序或命名空间时由 .NET Framework 公共语言运行库 (CLR) 自动加载。
使用静态类来包含不与特定对象关联的方法。例如,创建一组不操作实例数据并且不与代码中的特定对象关联的方法是很常见的要求。您应该使用静态类来包含那些方法。
静态类的主要功能如下:
-
它们仅包含静态成员。
-
它们不能被实例化。
-
它们是密封的。
-
它们不能包含实例构造函数(C# 编程指南)。
因此创建静态类与创建仅包含静态成员和私有构造函数的类大致一样。私有构造函数阻止类被实例化。
使用静态类的优点在于,编译器能够执行检查以确保不致偶然地添加实例成员。编译器将保证不会创建此类的实利。
静态类是密封的,因此不可被继承。静态类不能包含构造函数,但仍可声明静态构造函数以分配初始值或设置某个静态状态7、命名空间别名限定符(global::)
这个代表了全局命名空间(最上层的命名空间),也就是任何一个程序的默认命名空间。
class TestApp
{
public class System
{
}
const int Console = 7;
static void Main()
{
//用这个访问就会出错,System和Console都被占用了 //Console.WriteLine("bc");
global::System.Console.WriteLine("bc");
}
}
8、外部程序集别名(extern alias)
extern 修饰符用于声明在外部实现的方法。extern 修饰符的常见用法是在使用 Interop 服务调入非托管代码时与DllImport 属性一起使用;在这种情况下,该方法还必须声明为static,如下面的示例所示:
[DllImport("avifil32.dll")]
private static extern void AVIFileInit();
备注:
extern 关键字还可以定义外部程序集别名,使得可以从单个程序集中引用同一组件的不同版本。有关更多信息,请参见
外部别名(C# 参考)。
在该示例中,程序接收来自用户的字符串并将该字符串显示在消息框中。程序使用从 User32.dll 库导入的 MessageBox 方法。
using System;
using System.Runtime.InteropServices;
class MainClass
{
[DllImport("User32.dll")]
public static extern int MessageBox(int h, string m, string c, int type);
static int Main()
{
string myString;
Console.Write("Enter your message: ");
myString = Console.ReadLine();
return MessageBox(0, myString, "My Message Box", 0);
}
}
详情参考: extern(C# 参考)
9、属性访问器可访问性
属性或索引器的 get 和 set 部分称为“访问器”。默认情况下,这些访问器具有相同的可见性或访问级别:其所属属性或索引器的可见性或访问级别。有关更多信息,请参见可访问性级别。不过,有时限制对其中某个访问器的访问会很有用。通常是在保持get 访问器可公开访问的情况下,限制set 访问器的可访问性,如下:
public string Name
{
get
{
return name;
}
protected set
{
name = value;
}
}
更多详情: 非对称访问器可访问性(C# 编程指南)
10、委托中的协变和逆变(covariance and contravariance)
概述
将方法签名与委托类型匹配时,协变和逆变为您提供了一定程度的灵活性。协变允许方法具有的派生返回类型比委托中定义的更多。逆变允许方法具有的派生参数类型比委托类型中的更少。
示例一(协变)
本示例演示如何将委托与具有返回类型的方法一起使用,这些返回类型派生自委托签名中的返回类型。由 DogsHandler返回的数据类型是 Dogs 类型,它是由委托中定义的Animals类型派生的。
public class Animals
{
}
public class Dogs : Animals
{
}
class Program
{
public delegate Animals AnimalsMethod();
public delegate Dogs DogsMethod();
public static Animals AnimalsHandler()
{
return null;
}
public static Dogs DogsHandler()
{
return null;
}
static void Main(string[] args)
{
AnimalsMethod animalshandler = AnimalsHandler;
AnimalsMethod dogshandler = DogsHandler;
//如下两句出错
//DogsMethod dogshandler = DogsHandler;
//animalshandler = dogshandler;
}
}
示例二(逆变)
本示例演示如何将委托与具有某个类型的参数的方法一起使用,这些参数是委托签名参数类型的基类型。通过逆变,以前必须使用若干个不同处理程序的地方现在只要使用一个事件处理程序即可 。如,现在可以创建一个接收 EventArgs 输入参数的事件处理程序,然后,可以将该处理程序与发送 MouseEventArgs 类型(作为参数)的 Button.MouseClick 事件一起使用,也可以将该处理程序与发送 KeyEventArgs 参数的 TextBox.KeyDown 事件一起使用。
System.DateTime lastActivity;
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;
}
说明:协变针对委托的返回值,逆变针对参数,原理是一样的。
其他特性还有:友元程序集(Friend Assembly)、固定大小的缓冲区(fixed关键字)、volatile关键字、#pragm warning等,具体的细节查看下面给出的参考资料
参考资料