C#程序设计.唐大仕.04.C#高级语言特性

C#高级语言特性

1. 委托

  • delegate
  • 大致上:委托 \(\approx\) 函数指针

委托是对函数原型的包装

  • 委托的声明
1
public delegate double MyDelegate(double x);
  • 委托的实例化
1
2
// 可以是对象的某个方法, 也可以是类的某个静态方法
MyDelegate d2 = new MyDelegate(obj.myMethod);
  • 委托的调用
    • 委托变量名(参数列表)
1
d2(8.9);

一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 声明一个委托
delegate double Fun(double x);

public class DelegateIntegral {

public static void Main() {
// 类的静态方法
Fun fun = new Fun(Math.Sin);
double d = Integral(fun, 0, Math.PI / 2, 1e-4);
Console.WriteLine(d);

// 静态方法
Fun fun2 = new Fun(Linear);
double d2 = Integral(fun2, 0, 2, 1e-3);
Console.WriteLine(d2);

Rnd rnd = new Rnd();
// 类的方法
double d3 = Integral(new Fun(rnd.Num), 0, 1, 0.01);
Console.WriteLine(d3);
}

private static double Linear(double a) {
return a * 2 + 1;
}

private class Rnd {
private Random r = new Random();
public double Num(double x) {
return r.NextDouble();
}
}

// 积分计算
private static double Integral(Fun f, double a, double b, double eps) {
// ...
fa = f(a); // 使用委托
fb = f(b);
// ...
}
}
  • C# 4 以上版本定义了很多的委托
  • 例如
1
2
3
4
5
6
// 没有返回值, 封装的委托从 0 个参数到 16 个参数
delegate void System.Action();
delegate void System.Action<in T> (T obj);

// 有返回值, 封装的委托从 0 个参数到 16 个参数
delegate TResult System.Func<in T, out TResult>(T arg);
  • 我们可以对上面的例子做如下修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ...
public static void Main() {
Func<double, double> fun = new Func<double, double>(Math.Sin);
double d = Integral(fun, 0, Math.PI / 2, 1e-4);
Console.WriteLine(d);

Func<double, double> fun2 = new Func<double, double>(Linear);
double d2 = Integral(fun2, 0, 2, 1e-3);
Console.WriteLine(d2);

Rnd rnd = new Rnd();
double d3 = Integral(new Func<double, double>(rnd.Num), 0, 1, 0.01);
Console.WriteLine(d3);
}
// ...
private static double Integral(Func<double, double> f, double a, double b, double eps) {
// ...
}

委托的合并

  • 委托的合并----多播MultiCastDelegate
    • 一个委托实例中可以 “包含” 多个函数
    • 调用委托,就是调用其中多个函数
    • 多个函数间的先后顺序是没有意义的
    • 返回值也就没有太多意义
  • 运算符:+-+=-=
    • 动态地增减其中的函数
    • 提高了程序的灵活性
  • 例子

委托的转换与相等

  • 委托的转换

    • 按声明的名称判断
    • 以下两个不能互相转换或加减

    1
    2
    delegate void D(int a);
    delegate void E(int a);

  • 委托的相等

    • 按内容(即其中“包含的函数”)来判断
    • 有点点像两个字符串的 “相等” 与否的判断
  • 一些测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
D cd1 = new D(C.M1);
D cd2 = new D(C.M1);
D cd3 = null;
cd3 += new D(C.M1);
D cd4 = new D(C.M1) + new D(C.M1);
E cd5 = new E(C.M1);

Console.WriteLine(cd1.Equals(cd2)); // True
Console.WriteLine(cd1.Equals(cd3)); // True
Console.WriteLine(cd1.Equals(cd4)); // False
Console.WriteLine(cd1.Equals(cd5)); // False

D cd6 = new D(C.M1) + new D(C.M2);
D cd7 = new D(C.M2) + new D(C.M1);
Console.WriteLine(cd6.Equals(cd7)); // False

总结

  • 委托相当于函数指针
  • 但它类型更安全,是引用类型
  • 且功能更强大,有多播功能

2. 事件

  • 大致上:事件 \(\approx\) 回调函数

GUI 中的事件

1
2
3
4
5
this.button1.Click += new System.EventHandler(this.button1_Click);

private void button1_Click(object sender, EventArgs e) {
// ...
}

自定义事件

  • 事件的声明
1
public event 委托名 事件名;
  • 事件的注册与移除
    • 事件名 +=-=
      • 在事件所在类的外面,只能用以上两个运算符
  • 事件的发生(激发)
    • 事件名(参数列表)
    • 相当于回调所注册的函数

实例

定义及使用事件的 6 步曲

  • 声明事件参数类: class xxxEventArgs{}
  • 声明委托:delegate void xxxEventHandler(obj, args)
  • 定义事件:public event 类型 名称
  • 发生事件:事件名(参数)
  • 定义一个方法: void 方法名(obj, args)
  • 注册事件:xxx.事件+= new 委托(方法名)

事件与委托的关系

  • 事件有点像委托类型的实例

    • 事件一定有相关的委托类型
    • 与委托实例一样,事件也“包含”多个函数
    • 事件的运算受更多限制(在类外只能用 +=-=
  • 事件比委托实例更复杂

    • 可以定义事件存取器

    1
    2
    3
    4
    修饰符 event 委托类型名 事件名 {
    add{e += value; }
    remove{ e -= value; }
    }

事件总结

  • 事件是一种消息机制
  • 事件源调用事件,别的类注册事件
  • 事件的类型是一个委托

3. lambda 表达式

  • C# 语言新特性
    • C#2.0 引入泛型
    • C#3.0 引入 Lambda 及 Linq
    • C#4.0 更多的动态特性 dynmaic

泛型

  • Generic
1
2
3
4
5
6
List<Book> books = new List<Book>();
Book book = books[0];

// 以前要用强制类型转换
ArrayList books = new ArrayList();
Book book = (Book)books[0];

匿名方法

1
delegate(参数){ 方法体; }
  • 可以当一个匿名方法
1
2
3
new Thread(new ThreadStart(delegate(){
// ...
})).Start();
  • 可以被隐式转换为一个兼容的委托类型
    • 省略 ThreadStart
1
2
3
new Thread(delegate() {
// ...
}).Start();

lambda 表达式

  • 相当于匿名方法的简写
    • 省略 delegate,甚至省略参数类型
1
(参数) => { 语句或表达式; }
  • 例子
1
2
3
new Thread(()=>{
// ...
}).Start();
1
2
3
button1.Click += (sender,e) => {
// ...
};

lambda 表达式 vs 匿名方法

  • lambda 表达式比匿名函数简单
  • 匿名函数多一个功能:
    • 不写 (参数) 的匿名函数,可以转成任意多个参数的委托

Linq

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
List<Program> programs = new List<Program>();
for (int i = 0; i < 10; ++i) {
programs.Add(new Program());
}

// Linq
var a = from p in programs
where p.RunTime < 50
orderby p.Name
select p.Name;
foreach (var i in a) {
Console.WriteLine(i);
}


// Lambda 表达式
var b = programs
.Where(p => p.RunTime < 50)
.OrderBy(p => p.Name)
.Select(p => p.Name);
foreach (var i in a) {
Console.WriteLine(i);
}

总结

  • 匿名函数使用 delegate
  • Lambda表达式使用 =>
  • Linq 使用 from, where, select
  • 对比代码

4. 运算符重载

  • 运算符重载有一些限制
    • 如成对(true/false),如类型要求,如有的不能重载
  • 运算符的声明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 一元运算符
public static 类型 operator 一元运算符(类型 参数名){
// ...
}

// 二元运算符
public static 类型 operator 二元运算符(类型 参数名, 类型 参数名) {
// ...
}

// 类型转换运算符
public static implicit operator 类型 (类型 参数名) {
// ..
}
public static explicit operator 类型 (类型 参数名) {
// ...
}

5. 异常处理

异常

1
2
3
4
5
6
7
try {
// ...
} catch(Eception e) {
// ...
} finally {
// ...
}
  • 不管是否有异常,都会执行到 finally
    • 即使在 tryreturn 了,也会执行 finally
    • 代码
  • System.Exception
1
2
3
4
5
6
7
// 方法
public Exception();
public Exception(string s);

// 属性
Message;
StackTrace;

系统内部异常类

1
2
3
4
5
6
7
8
9
10
11
System.OutOfMemoryException
System.StackOverflowException
System.NullReferenceException
System.TypeInitializationException
System.InvalidCastException
System.ArrayTypeMismatchException
System.IndexOutOfRangeException
System.MulticastNotSupportedException
System.ArithmeticException
System.DivideByZeroException
System.OverflowException

捕获和处理异常

1
2
3
4
5
6
7
8
9
10
11
try{
// ...
} catch(AException e1){
// ...
} catch(BException e2){
// ...
} catch(更一般的Exception e){
// ...
} finally {
// ...
}
  • catch{} 表示捕获所有种类的异常
1
2
3
4
5
try {
// ...
} catch {
// ...
}

抛出异常

1
2
3
if(xxxxxx) {
throw new SomeException(信息);
}

自定义异常类

  • ExceptionApplicationException 继承
  • 重抛异常
1
throw;
  • 异常链接
1
throw new Excepiton("msg", e);
  • 这里 e 称为内部异常
  • InnerException 属性
  • 使得外部能进一步知道内部的异常原因

算术溢出与 checked

  • 对溢出进行检查
    • 对整个程序 csc /checked
    • 对部分程序
      • 针对表达式: checked(表达式) 及 uncheckd(表达式)
      • 针对块语句: checked{...} 及 uncheckd{...}
    • 对溢出异常进行捕获
      • try{ ... } catch( OverflowException e ) { ... }

6. Attribute

  • 用在类上
1
2
3
4
5
6
7
[ComVisible(true)]
[DefaultMember("Chars")]
public sealed class String : IComparable, ICloneable,
IConvertible, IEnumerable, IComparable<String>,
IEnumerable<char>, IEquatable<String> {
// ...
}
  • 用在方法上的
1
2
[SecuritySafeCritical]
public String(char[] value);
1
2
3
4
[STAThread]
static void Main() {
// ...
}
  • Attribute 是与类、结构、方法等元素相关的额外信息,是对元信息的扩展
  • 通过Attribute可以使程序、甚至语言本身的功能得到增强

使用系统定义的 Attribute

  • 使用 Attribute 的一般方式
    • 在程序集、类、域、方法等前面用[]表示
    • 可以省略“Attribute”几个字母,只写xxxxx
    • 可以带参数
      • 位置参数 (相当于构造方法带的参数)
      • 命名参数(域名或属性名=值)
  • 示例
    • 在Main()方法使用[STAThread]
    • 在结构上、枚举上使用:StructLayout,Flag
    • 在程序集级别应用Attribute
      • [assembly: AssemblyCompany("")]

自定义Attribute

  • 声明 Attribute 类
    • 从 System.Attribute 继承而来
    • 名字要用 xxxxAttribute
  • 使用 Attribute 类
    • 在类及成员上面使用方括号
    • 可以省略后缀 Attribute
  • 通过反射访问属性
  • 实例

7. C# 语言中的其他成分

编译预处理

  • 标识符声明
1
2
3
4
5
// 定义一个标识符
#define

// 取消定义一个标识符。
#undef
  • 条件处理
1
#if, #elif, #else, #endif
  • 信息报告
1
#error, #warning
  • 行号标记
1
#line 行号 "文件名"

unsafe 及指针

  • unsafe
    • 用于修饰类、方法等
  • fixed 及指针
    • fixed(类型 * 指针名 = 表达式) 语句
  • sizeof运算符
    • sizeof(简单或结构类型名)
  • stackalloc
    • 在栈上分配的内存,而不是在堆上,因此不会担心内存被垃圾回收器自动回收。
  • 示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class FileStream: Stream {
int handle;

[dllimport("kernel32", SetLastError=true)]
static extern unsafe bool ReadFile(
int hFile, void* lpBuffer,
int nBytesToRead, int* nBytesRead, Overlapped* lpOverlapped);

public unsafe int Read(byte[] buffer, int index, int count) {
int n = 0;
fixed (byte* p = buffer) {
ReadFile(handle, p + index, count, &n, null);
}
return n;
}
}
  • UnsafeCopy.cs
    • unsafe 关键字需要在编译的时候加上 /unsafe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 编译时需要: /unsafe
using System;
class Test {
unsafe static string IntToString(int value) {
char* buffer = stackalloc char[16];
char* p = buffer + 16;
int n = value >= 0 ? value : -value;
do {
*--p = (char)(n % 10 + '0');
n /= 10;
} while (n != 0);
if (value < 0) *--p = '-';
return new string(p, 0, (int)(buffer + 16 - p));
}
static void Main() {
Console.WriteLine(IntToString(12345));
Console.WriteLine(IntToString(-999));
}
}

其他关键字

  • lock
    • 多线程程序中,lock可以将某个对象加锁
  • volatile
    • 随时可能被程序以外的其他因素所修改
    • 域被 volatile 修饰时,会阻止编译器对它的优化

8. 程序的组织

  • 名字空间:程序的逻辑组织
  • 嵌套类型:类中嵌套类型
  • 程序集:程序的物理组织

名字空间

  • 名字空间的概念

    • 逻辑划分;避免名字冲突
  • 名字空间的声明

    • 可嵌套
1
namespace xxx.xxxx { }
  • 名字空间的导入
1
using xxx.xxxx;
  • 使用别名
1
using 别名 = 名字空间或类名;

嵌套类型

  • 嵌套类型的概念
  • 类型中的类型
1
2
3
4
5
6
7
class A {
public class B {
public struct C {}
}
}

new A.B.C();
  • 嵌套类型的可访问性
    • 受各个层次的限制

程序集

  • 模块(module)
    • 程序集(assembly)
    • exe、dll
  • VS 生成一个程序集
    • 新建项目的时候指定为类库
  • 在 VS 上引用程序集
    • 在项目上点右键,添加引用

编译示例

1
2
3
4
5
6
7
csc /target:mod /out:Add.mod Add.cs

csc /target:mod /out:Multi.mod Multi.cs

al /target:library /out:MyLibrary.dll Add.mod Multi.mod

csc /target:exe /out:MyClient.exe /reference:MyLibrary.dll MyClient.cs

9. 语法总结

类型声明

  • 类型声明是 C# 程序的主体,它可以位于名字空间中,也可以是嵌套的类型
  • 类型声明包括以下几种:
    • 类 class
    • 结构 struct
    • 接口 interface
    • 枚举 enum
    • 委托 delegate

类的成员

  • 常数(const)
    • 它代表了与类相关的常数数据
  • 域(field)
    • 它是类中的变量
  • 方法(method)
    • 它实现了可以被类实现的计算和行为
  • 属性(property)
    • 它定义了命名的属性和与对这个属性进行读写的相关行为
  • 事件(event)
    • 它定义了由类产生的通知
  • 索引(indexer)
    • 它允许类的实例通过与数组相同的方法来索引
  • 运算符(operator)
    • 它定义了可以被应用于类的实例上的表达式运算符
  • 实例构造函数(instance constructor)
    • 它执行需要对类的实例进行初始化的动作
  • 析构函数(destructor)
    • 类的实例被清除时实现的动作(结构不能有析构函数)
  • 静态构造函数(static constructor)
    • 它执行对类本身进行初始化的动作
  • 类型(type)
    • 它代表位于类中的类型