C#编程

Posted by LCY on August 20, 2018

常用链接:


第一章 .NET之道

.NET可以理解为 运行库环境基础类库 概念:

  • COM: component object model, 组件对象模型
  • CLR: common language runtime, 公共语言运行库,主要为我们定位、加载和管理.NET类型
  • CTS: common type sytem, 公共类型系统,CTS完整的我描述了运行库所支持的所有可能的数据类型和编程结构,它指的是一个集合{类、接口、委托、枚举、结构},每个类型又是一个集合{构造函数、终结器、静态构造函数、嵌套类型、操作符、属性、索引器、字段、只读字段、常量、事件}中的一些元素
  • CLS: common language specification, 公共语言规范
  • BCL: base class library, 基础类库,封装了各种基本类型,如线程、文件输入和输出、图形绘制以及与各种硬件设备的交互
  • 托管语言:C#/VB/C++/JS/F#
  • 托管代码: managed code,简单来说就是必须运行在.NET运行库下的代码成为托管代码

第二章 构建C#应用程序

使用visual studio常用功能

  • 解决方案资源管理器
  • class view工具
  • object browser工具
  • 代码的重构
  • 可视化构建类(class designer)
  • 集成的SDK开发文档或帮助(这一点很重要,是解决很多问题的关键)

第三章 C#核心编程I

3.1 一个简单的程序

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class SimpleStringTest
    {
        static void Main(string[] args)
        {
            string a = "\u0068ello ";
            string b = "world";
            Console.BackgroundColor = System.ConsoleColor.Blue;
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine(a + b);
            Console.WriteLine(a + b == "Hello World"); // == performs a case-sensitive comparison
            //Console.ReadKey();
            Console.ReadLine();
        }
    }
}

批处理测试代码:

@echo off

rem A batch file for ConsoleApp1.exe
rem which captures the app's return value.

ConsoleApp1
@if "%ERRORLEVEL%"=="0" goto success

:fail 
 echo This application has failed
 echo return value=%ERRORLEVEL%
 goto end
:success
 echo This application has succeeded
 echo return value=%ERRORLEVEL%
 goto end
:end
 echo all done.
  • C#程序是区分字母大小写的程序设计语言,特别注意,同时养成良好的命名习惯,可以参考前面的博客
  • 若出现undefined symbols编译错误时,检查拼写是否错误
  • C#程序必须包含一个Main()方法,作为程序的入口,即使使用void返回,它也会隐形的返回0作为错误代码,作为程序成功执行的标志,它可以使用批处理代码来捕获

3.2 System.Environment类

3.3 System.Console类

关于类的属性、方法、事件等使用方法和内容,可以直接查看msdn,其中有详细的内容

3.3.1 格式化数值数据

  • 可以单独使用{}进行占位输出,同时不需要按照递增的次序
  • 也可以使用数值格式字符,如下表所示,调用格式 {0:f7}、{1:d2}
字符串格式 作用
c/C 格式化货币
d/D 用于格式化十进制数
e/E 用于指数格式化
f/F 用于浮点数

示例:

static void FormatNumericalData()
{
    Console.WriteLine("The value 99999 in various formats:");
    Console.WriteLine("c format: {0:c}", 99999);
    Console.WriteLine("d9 format: {0:d9}", 99999);
    Console.WriteLine("f3 format: {0:f3}", 99999);
    Console.WriteLine("n format: {0:n}", 99999);

    // Notice that upper- or lowercasing for hex
    // determines if letters are upper- or lowercase.
    Console.WriteLine("E format: {0:E}", 99999);
    Console.WriteLine("e format: {0:e}", 99999);
    Console.WriteLine("X format: {0:X}", 99999);
    Console.WriteLine("x format: {0:x}", 99999);
}

3.4 C#关键字

3.4.1 变量声名和初始化

  • 声名变量的同时就可以进行初始化,也可以先声名,要用之前就行初始化
  • 允许在一行代码中声名具有相同实际类型的变量,如bool b=true, bool c=false,bool d=false;

3.4.2 内建数据类型与new操作符

所有的内建数据类型都支持默认构造函数,因此可以使用new关键字来创建变量,体会 万物皆对象的思想,同时变量自动设置为其默认值。但这种方法会显得比较的笨重,很少采用。

bool b = new bool();//默认为false
DateTime dt = new DateTime();//默认为1/1/0001 12:00:00
string str = new string();//默认为null

3.4.3 数据类型类的层次结构

很多数值数据类型都派生自system.valuetype,但还有其他,如system.Aarry、system.String等,他们在分配变量时,处理方式是不同的,前者是在 栈上进行分配,而后者是在 垃圾回收堆上进行分配。

3.4.4 数值数据类型的成员

3.4.5 system.Boolean的成员

3.4.6 system.Char的成员

3.4.7 从字符串数据中解析数值

  • 就是使用parse方法
     public static char Parse(String s) 
     {
        if (s==null) 
        {
            throw new ArgumentNullException("s");
        } 
        Contract.EndContractBlock();
          
        if (s.Length!=1) 
        {
            throw new FormatException(Environment.GetResourceString("Format_NeedSingleChar"));
        } 
        return s[0];
    }
    

3.4.8 System.DateTime和System.TimeSpan

System.DateTime类型包含了表示某个日期的数据以及时间值,可以使用指定的成员以各种形式将他们格式化。System.TimeSpan结构允许你方便的使用各个成员定义和转换时间单位。

3.4.9 System.Numerics程序集

  • 可以使用这个程序集进行一些大数的操作。

3.5 System.String类

  • 其中包括一些对字符串基本的操作,因为在编写程序的过程中,经常需要使用到对字符串操作的方法,这一部分内容需要重要掌握
  • 理解字符串的 不可变性,使用方法处理后的字符串是一个新的字符串变量,如果输出处理前的变量,依然是不变的

3.6 窄化和宽化数据类型转换

3.7 隐式类型本地变量

主要就是关键字(其实算不上关键字)var的使用,使用它作为变量名也是不会出错的,所以它算不上是c#中的一个关键字

3.8 C#的迭代结构

  • for结构
  • foreach结构
  • while和do/while结构 可以配合continue/break/goto等关键字实现更加复杂的程序功能,语言集成查询(LINQ):language integrated query

3.9 条件结构和关系/相等操作符

  • if/else语句
  • switch语句 当分支结果比较多的情况下,可以优先考虑使用switch语句结构

第四章 C#核心编程II

4.1 方法和参数修饰符

  • 静态方法可以被直接调用,无需创建类的实例
class Program
{static in Add(int x, int y){return x+y}}
  • C#参数修饰符包括 (无)\out\ref\params

4.1.1 默认的参数传递行为

note:参数传入函数的默认行为是 按值传递,如果在成员的作用于内修改参数的值,改变的就是调用镇数据值得 副本

static void Main(string[] args)
{
    int x = 10;
    int y = 20;
    Console.WriteLine("before call: x={0},y={1}", x, y);
    Console.WriteLine("answer is:{0}", Add(x, y));
    Console.WriteLine("before call: x={0},y={1}", x, y);
    Console.ReadLine();
    
    //return -1;
}
static int Add(int x, int y)
{
    int ans = x + y;
    x = 1; y = 2;
    return ans;
}

运行结果:

before call: x=10,y=20
answer is:30
before call: x=10,y=20

4.1.2 out修饰符

note:它存在的主要意义在于调用者只使用一次方法调用就能获得 多个返回值

static void FillTheseValues(out int a, out sting b, out bool c)
{a=9;b="liaochengyu";c=false;}

4.1.3 ref修饰符

note:如果希望方法可以对在调用者作用域中声名的不同数据进行操作,例如排序和交换例程中,就必须使用引用参数

static void Main(string[] args)
{
    int x = 10;
    int y = 20;
    Console.WriteLine("before call: x={0},y={1}", x, y);
    Console.WriteLine("answer is:{0}", Add(ref x, ref y));
    Console.WriteLine("before call: x={0},y={1}", x, y);
    Console.ReadLine();
    
    //return -1;
}
static int Add(ref int x, ref int y)
{
    int ans = x + y;
    x = 1; y = 2;
    return ans;
}

运行结果:

before call: x=10,y=20
answer is:30
before call: x=1,y=2//原始数据已经发生了改变

4.1.4 params修饰符

note:为了避免歧义,c#要求方法只支持一个params参数,而且必须是参数列表中的最后一个参数。

4.1.5 定义可选参数

note:这项技术允许方法的调用者不指定不必要的参数,而是使用这些参数的默认值。同时,分配给可选参数的值必须在编译的时候确定。

4.1.6 使用命名参数调用方法

note:可选参数和命名参数往往会一起使用。

4.1.7 成员重载

note重载,简而言之,当我们定义了一组名字相同的成员时,如果他们的参数数量或者类型不同,这样的成员就叫做被重载。

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("***** Fun with Method Overloading *****\n");
        // Calls int version of Add()
        Console.WriteLine(Add(10, 10));
        // Calls long version of Add()
        Console.WriteLine(Add(900000000000, 900000000000));
        // Calls double version of Add()
        Console.WriteLine(Add(4.3, 4.4));
        Console.ReadLine();
    }
    #region Overloaded Add() method
    // Overloaded Add() method.
    static int Add(int x, int y)
    { return x + y; }
    static double Add(double x, double y)
    { return x + y; }
    static long Add(long x, long y)
    { return x + y; }
    #endregion
}

4.2 C#数组

4.2.1 C#数组的初始化语法

notestring[] str=new string[] {"liao","cheng","yu"},使用{}语法时,不需要指定数组的大小。

4.2.2 隐式类型本地数组

note:隐式语法分配数组的时候,数组的初始化列表中每一项的类型都应该是相同的,默认不是system.object

4.2.3 定义object数组

note:system.object是.NET类型系统中所有类型的最终积累,基于这一点,如果定义object数组,子项就可以是任何东西。

4.2.4 使用多维数组

note:C#支持两种多维数组,主要是区分他们 初始化的方式,以及使用的范围

  • 矩形数组,它只是一个每一行长度都相同的多维数组 int[,] matrix=new int[6,2]

  • 交错数组,包含一些内部数组,每一个都有各自的上界 int[][] matrix=new int[5][]

4.2.5 数组作为参数和返回值

4.2.6 system.Array基类

note:注意,system.array的很多成员都是定义为静态方法,因此可以在类级别进行调用。sytem.array的其他方法如:length属性绑定在对象级别上,因此我们可以直接在数组上调用成员。

4.3 枚举类型

4.3.1 控制枚举类型的底层存储

note:自定义枚举类型,元素之间使用,进行分隔,同时默认编号从0开始,当然也可以自己指定或者不连续,也不需要有唯一值。默认为int型,可以根据实际的需要更改数据类型,如short、long、byte

enum EmpType:byte(类型)
{
    Manager,       // = 0
    Grunt,         // = 1
    Contractor,    // = 2
    VicePresident  // = 3
}

4.3.2 声名枚举变量

4.3.3 system.enum类型

note

  • 如果有实例,可以使用GetType()来获取对象的类型
  • 如果没有实例,也可以使用typeof()操作符来获取类型

4.4 结构类型(struct)

note:结构类型很适合在应用程序中对数学、集合以及其他“原子”实体建模。而且和枚举类型是比较相似的。它的使用类似于类的使用,也可以使用构造函数进行新建对象。

4.5 值类型和引用类型

4.6 C#可空类型

note:在涉及数据库编程时,可空数据类型可能特别的有用,因为一个数据表中的列可能有意是空的。

  • 4.6节测试代码: ```matlab #region A class with nullable data class DatabaseReader { // Nullable data field. public int? numericValue = null; public bool? boolValue = true;

    // Note the nullable return type. public int? GetIntFromDatabase() { return numericValue; }

    // Note the nullable return type. public bool? GetBoolFromDatabase() { return boolValue; } } #endregion

class Program { static void Main(string[] args) { Console.WriteLine(“*** Fun with Nullable Data ***\n”); DatabaseReader dr = new DatabaseReader();

    int myData = dr.GetIntFromDatabase() ?? 100;
    Console.WriteLine("Value of myData: {0}", myData);

    // Get int from "database".
    int? i = dr.GetIntFromDatabase();
    if (i.HasValue)
        Console.WriteLine("Value of 'i' is: {0}", i.Value);
    else
        Console.WriteLine("Value of 'i' is undefined.");

    // Get bool from "database".
    bool? b = dr.GetBoolFromDatabase();
    if (b != null)
        Console.WriteLine("Value of 'b' is: {0}", b.Value);
    else
        Console.WriteLine("Value of 'b' is undefined.");
    Console.ReadLine();
}

#region Declaring nullable varaibles
static void LocalNullableVariables()
{
    // Define some local nullable types.
    int? nullableInt = 10;
    double? nullableDouble = 3.14;
    bool? nullableBool = null;
    char? nullableChar = 'a';
    int?[] arrayOfNullableInts = new int?[10];

    // Error! Strings are reference types!
    // string? s = "oops";
}

static void LocalNullableVariablesUsingNullable()
{
    // Define some local nullable types using Nullable<T>.
    Nullable<int> nullableInt = 10;
    Nullable<double> nullableDouble = 3.14;
    Nullable<bool> nullableBool = null;
    Nullable<char> nullableChar = 'a';
    Nullable<int>[] arrayOfNullableInts = new int?[10];
}
#endregion } ```

第五章 封装

5.1 C#类类型

就.NET平台而言,最基本的编程结构就是类类型。正式地说,类是由字段数据(通常叫做成员变量)以及操作这个数据的成员(如构造函数、属性、方法、事件等)所构成的自定义类型。接下来构建一个简单的类:

class Car
{
    // The 'state' of the Car.
    public string petName;
    public int currSpeed;

    #region Constructors
    // A custom default constructor.
    public Car()
    {
        petName = "Chuck";
        currSpeed = 10;
    }

    // Here, currSpeed will receive the
    // default value of an int (zero).
    public Car( string pn )
    {
        petName = pn;
    }

    // Let caller set the full state of the Car.
    public Car( string pn, int cs )
    {
        petName = pn;
        currSpeed = cs;
    } 
    #endregion

    // The functionality of the Car.
    public void PrintState()
    {
        Console.WriteLine("{0} is going {1} MPH.", petName, currSpeed);
    }

    public void SpeedUp( int delta )
    {
        currSpeed += delta;
    }
}

note:类的字段数据很少定义为公共的(public),为了保证状态数据的完整性,最好将数据定义为 私有的或者是 受保护的,并且通过类型属性对数据提供受控制的访问。

从之前的代码示例中我们可以看到,对象必须使用new关键字来分配到内存中。

5.2 构造函数

note:我的理解,简单可以理解为为类的变量提供初始化的值,构造函数是特殊的方法,在使用new关键字创建对象时被间接调用。然而,和普通方法不同,构造函数永远不会有 返回值(即使是void),并且它的名字总是和需要构造的类的名字相同。

5.3 this关键字的作用

C#支持this关键字来提供对当前类实例的访问。this官架子可能的用途就是,解决当传入参数的名字和类型数据字段的名字相同时产生的作用域歧义。在没有歧义的情况下使用也是可以的,它还会触发IDE的智能感知功能,便于我们的编程。

5.3.1 使用this进行串联构造函数调用

note:需要注意的是,使用this关键字串联构造函数不是强制的。但如果使用这项技术,类定义就会更加容易维护、简明。使用这项技术可以简化编程任务,因为真正的工作都交给了一个构造函数(通常这个构造函数有大多数参数)来做,看下面例子:

public Motorcycle( int intensity )
    : this(intensity, "")
{
    Console.WriteLine("In ctor taking an int");

public Motorcycle( string name )
    : this(0, name)
{
    Console.WriteLine("In ctor taking a string");
}
// This is the 'master' constructor that does all the real work.
public Motorcycle( int intensity, string name )
{
    Console.WriteLine("In master ctor ");
    if (intensity > 10)
    {
        intensity = 10;
    }
    driverIntensity = intensity;
    driverName = name;
}

在串联构造函数时,请注意this如何在构造函数本身的作用域之外“躲开”构造函数的声名,通过冒号:操作符。

5.3.2 观察构造函数流程

5.3.3 再谈可选参数

可选/命名参数灵巧的简化了给定类定义构造函数集的方式,但要记住的是这种语法只能在.NET4.0或更高版本的平台下运行。如果你需要构建一个在任何版本的平台下都能工作的类,最好坚持使用传统的构造函数链技术。

5.4 static关键字

note

  • C#类或者结构可以通过static关键字来定义许多静态成员。如果这样的话,这些成员就只能直接从类级别而不是对象引用调用。
  • 简而言之,静态方法被认为是非常普遍的方法,并且不需要再调用成员时创建类型的实例。
  • static关键字可以使用在类的数据、方法、属性、构造函数以及整个类。

5.4.1 定义静态数据

note:大部分情况下,我们将数据定义为实例级别的数据,也就是非静态数据。同时静态数据字段是由所有的对象共享的。

5.4.2 定义静态方法

note

  • 如果你要定义一个所有对象都可以分享的数据点,就可以使用静态成员
  • 如果同种类的所有对象都经常使用某个值,也是非常适合使用静态数据的。
  • 静态成员在其实现中引用非静态成员会导致编译器错误,我的理解就是如果写一个静态方法,就不能再方法中使用非静态的数据,不知道这么理解对不?

5.4.3 定义静态构造函数

  • 一个类只可以定义一个静态构造函数,不能被重载
  • 不允许访问修饰符并且不能接受任何参数
  • 无论创建了多少类型的对象,静态构造函数只执行一次
  • 运行库创建类实例或调用者首次访问静态成员之前,运行库会调用静态构造函数
  • 静态构造函数的执行先于任何实例级别的构造函数

5.4.4 定义静态类

如果一个类被定义为静态的,就不能使用new关键字来创建,并且只能包含用static关键字标记的成员或字段。只包含静态功能的类或结构通常称为工具类。

5.5 定义OOP的支柱

  • 封装(encapsulation):怎么隐藏一个对象的内部实现并且保护数据的完整性
  • 继承(inheritance):怎么促进代码重用的
  • 多态(polymorphism):怎么让你用同样的方式处理相关对象的

5.6 C#访问修饰符

note:非嵌套类型只能用public或者internal修饰符定义。

5.7 第一个支柱:C#的封装服务

note:封装概念的核心,对象的内部数据不应该从对象实例直接访问。 定义私有字段的主要方式有以下两种:

  • 定义一对传统的访问方法get(也称为获取方法)和修改方法set(也称为设置方法)。
  • 定义一个.NET属性。 封装良好的类应该对外部世界隐藏操作数据方式的细节。通常称为 黑盒编程black box programming),优势在于可以自由的改变一个给定方法在底层的实现方式,而不影响任何已存在的使用它的代码。

5.7.1 使用传统的访问方法和修改方法执行封装

5.7.2 使用.NET属性进行封装

public string Name
{
    get { return empName; }
    set
    {
        if (value.Length > 15)
            Console.WriteLine("Error!  Name must be less than 16 characters!");
        else
            empName = value;
    }
}

note:使用get/set/value(上下文关键字)来实现对字段的封装,注意区别于 方法,在属性名后面没有()

调用方式:

// Set and get the Name property.
emp.Name = "Marv";  //其实是调用了set方法
Console.WriteLine("Employee is named: {0}", emp.Name);

5.7.3 使用类的属性

5.7.4 只读和只写属性

note:选择性的忽略get或者set语句即可。

5.7.5 静态属性

使用static关键字去修饰属性。

5.8 自动属性

public string petName{get;set;}
public int speet{get;set;}
public string color{get;set;}

note

  • 自动属性的存在,主要针对于在set作用域内不包含业务代码,且需要创建的数据点又比较多的情况下。
  • vs提供了prop代码段,自动生成自动属性代码。
  • 自动属性必须同时支持读写功能,不能定义只读或者只写属性。

5.9 对象初始化语法 (这个地方需要再理解一下)

5.10 常量数据

note

  • C#中提供了const关键字来定义常量(他在赋初始值后就没有变过)
  • 类的常量字段是隐式静态的
  • 还有一点就是定义常量时必须为常量指定初始值,不能先声明后赋值,如下面的 例子
    class MyMathClass
    {
      public const double PI;
      public MyMathClass()
      {PI=3.14;//错误}
    }
    

5.11 分部类型

note:主要针对一个类文件比较大的时候,将字段和方法等东西分解开来,方便管理,一般来说,是使用不到的。

第六章 继承和多态

6.1 继承的基本机制

note:继承的本质就是促进代码的重用,具体地说,代码重用归为两类:继承(is-a关系)和包含/委托模型(has-a关系)。

6.1.1 指定既有类的父类

在C#中,在类定义中利用冒号操作符在类之间创建is-a关系。

class MiniVan:Car
{}

note

  • 虽然MiniVan类当前没有任何成员,但MiniVan对象已经拥有了定义在父类中的每一个公共成员的访问权限。
  • 可以在派生类中访问父类的公共成员,而不能访问父类的私有成员

6.1.2 多个基类

note

  • C#只支持派生类仅有一个基类
  • 而非托管语言,如C++等允许派生于多个基类,这项技术叫做 多重继承
  • 可以通过一个接口可以从多个接口派生,使用这个特性可以达到多重继承的作用

6.1.3 sealed关键字

note

  • C#使用关键字sealed来防止发生继承。
  • 在C#中结构总是隐式密封的,因此结构是不存在继承一说的

6.2 回顾visual studio 类关系图

note:使用vs自带的 类关系图类设计工具箱类详细信息等工具,可以实现可视化的设计类,可以自动生成代码,缩短开发时间,简化设计的过程。

6.3 第二个支柱:继承

6.3.1 使用base关键字控制基类的创建

note:只要子类想访问有父类定义的公共或受保护的成员,我们就可以使用base关键字,而且不仅仅只有在构造函数中使用这个关键字。

6.3.2 家族的秘密:protected关键字

note

  • 好处在于:派生类型不在需要使用公共方法或者属性来间接访问数据
  • 受保护数据可以认为是私有的,尽管protected关键字会打破封装,但定义protected方法是安全和有用的

6.3.3 增加密封类

用于 阻断继承分支,防止类继续的拓展,因为没有意义或价值

6.4 包含/委托编程

note

  • 也就是has-a关系
  • 嵌套类型定义,我们可以直接在类或者结构的作用域中定义类型(枚举、类、接口、结构、委托)

6.5 OOP的第三个支柱:多态

6.5.1 virtual和override关键字

note

  • 多态为子类提供了一种方式,是其可以定义由其基类定义的方法,这种过程叫做方法的重写。
  • 使用virtual关键字修饰方法,表示在基类中,可以使用override关键字对其方法进行重写

6.5.2 使用visual studio IDE重写虚方法

note:在visual studio键入override关键字时,IDE会自动感知在父类中可能需要重写的方法。

6.5.3 密封虚成员

note:不仅可以密封整个类,还可以密封类中的成员,如方法

6.5.4 抽象类

note:使用abstract关键字用于创建抽象类,抽象类不能创建实例

6.5.5 定义多态接口

note

  • 使用abstract关键字修饰方法,将方法标记为抽象的,即不提供任何实现,强制每一个子类重写定义为抽象的方法
  • 抽象方法只可以定义在抽象方法中,不然会出现编译错误

6.6 基类/派生类的转换规则

6.6.1 C#的as关键字

6.6.2 C#的is关键字

6.7 超级父类:system.object

note:在.NET中,每一个类型最终都会从system.object的基类派生,如果定义类时,没有显示的定义其父类,默认system.object为父类


第七章 结构化异常处理


第八章 接口

8.1 接口类型


第十章 委托、事件和lambda

10.1 .NET委托类型

10.1.1 在C#中定义委托类型

note:需要使用delegate关键字,委托的名称可以自由选择。

//比如下面这个委托可以指向任何传入两个整数返回一个整数的方法 public delegate BinaryOp(int x, int y)

简而言之,C#委托类型定义会生成一个密封类,它含有3个编译器生成的方法,这3个方法的参数与返回值基于委托的声名。

10.1.2 system.multicastdelegate与system.delegate基类

  • 自定义委托继承关系是:System.MulticastDelegate —> System.Delegate —>System.Object
  • 可以[参考博文:C#基础温习: 理解委托和事件]http://blog.jobbole.com/101798/)

10.2 最简单的委托示例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SimpleDelegate
{
    // This delegate can point to any method,
    // taking two integers and returning an integer.
    public delegate int BinaryOp( int x, int y );

    #region SimpleMath class
    // This class contains methods BinaryOp will
    // point to.
    public class SimpleMath
    {
        public int Add( int x, int y )
        { return x + y; }
        public int Subtract( int x, int y )
        { return x - y; }
        public static int SquareNumber( int a )
        { return a * a; }
    } 
    #endregion

    class Program
    {
        static void Main( string[] args )
        {
            Console.WriteLine("***** Simple Delegate Example *****\n");

            // Create a BinaryOp delegate object that
            // "points to" SimpleMath.Add().
            SimpleMath m = new SimpleMath();

            BinaryOp b = new BinaryOp(m.Add);
            DisplayDelegateInfo(b);

            // Invoke Add() method indirectly using delegate object.
            Console.WriteLine("10 + 10 is {0}", b(10, 10));

            // Compiler error! Method does not match delegate pattern!
            // BinaryOp b2 = new BinaryOp(SimpleMath.SquareNumber);

            Console.ReadLine();
        }

        #region Display delegate info
        static void DisplayDelegateInfo( Delegate delObj )
        {
            // Print the names of each member in the
            // delegate's invocation list.
            foreach (Delegate d in delObj.GetInvocationList())
            {
                Console.WriteLine("Method Name: {0}", d.Method);
                Console.WriteLine("Type Name: {0}", d.Target);
            }
        } 
        #endregion

    }
}

10.3 使用委托发送对象状态通知