白话C#

目录

特性

接口

泛型

多线程

反射

委托与事件

1.特性

首先要说的是,可能一些刚接触C#的朋友常常容易把属性(Property)跟特性(Attribute)弄混淆,其实这是两种不同的东西。属性就是面向对象思想里所说的封装在类里面的数据字段,其形式为:

   1: public class HumanBase
   2: {
   3:     public string Name { get; set; }
   4:     public int Age { get; set; }
   5:     public int Gender { get; set; }
   6: }

在HumanBase这个类里出现的字段都叫属性(Property),而特性(Attribute)又是怎样的呢?

   1: [Serializable]
   2: public class HumanBase
   3: {
   4:     public string Name { get; set; }
   5:     public int Age { get; set; }
   6:     public int Gender { get; set; }
   7: }

简单地讲,我们在HumanBase类声明的上一行加了一个[Serializable],这就是特性(Attribute),它表示HumanBase是可以被序列化的,这对于网络传输是很重要的,不过你不用担心如何去理解它,如何理解就是我们下面要探讨的。

C#的特性可以应用于各种类型和成员。前面的例子将特性用在类上就可以被称之为“类特性”,同理,如果是加在方法声明前面的就叫方法特性。无论它们被用在哪里,无论它们之间有什么区别,特性的最主要目的就是自描述。并且因为特性是可以由自己定制的,而不仅仅局限于.NET提供的那几个现成的,因此给C#程序开发带来了相当大的灵活性和便利。

我们还是借用生活中的例子来介绍C#的特性机制吧。

假设有一天你去坐飞机,你就必须提前去机场登机处换登机牌。登机牌就是一张纸,上面写着哪趟航班、由哪里飞往哪里以及你的名字、座位号等等信息,其实,这就是特性。它不需要你生理上包含这些属性(人类出现那会儿还没飞机呢),就像上面的HumanBase类没有IsSerializable这样的属性,特性只需要在类或方法需要的时候加上去就行了,就像你不总是在天上飞一样。

当我们想知道HumanBase是不是可序列化的,可以通过:

   1: static void Main(string[] args)
   2: {
   3:     Console.WriteLine(typeof(HumanBase).IsSerializable);
   4:  
   5:     Console.ReadLine();
   6: }

拿到了登机牌,就意味着你可以合法地登机起飞了。但此时你还不知道你要坐的飞机停在哪里,不用担心,地勤人员会开车送你过去,但是他怎么知道你是哪趟航班的呢?显然还是通过你手中的登机牌。所以,特性最大的特点就是自描述。

既然是起到描述的作用,那目的就是在于限定。就好比地勤不会把你随便拉到一架飞机跟前就扔上去了事,因为标签上的说明信息就是起到限定的作用,限定了目的地、乘客和航班,任何差错都被视为异常。如果前面的HumanBase不加上Serializable特性就不能在网络上传输。

我们在顺带来介绍一下方法特性,先给HumanProperty加上一个Run方法:

   1: [Serializable]
   2: public class HumanPropertyBase
   3: {
   4:     public string Name { get; set; }
   5:     public int Age { get; set; }
   6:     public int Gender { get; set; }
   7:  
   8:     public void Run(int speed)
   9:     {
  10:         // Running is good for health.
  11:     }
  12: }

只要是个四肢健全、身体健康的人就可以跑步,那这么说,跑步就是有前提条件的,至少是四肢健全,身体健康。由此可见,残疾人和老年人如果跑步就会出问题。假设一个HumanBase的对象代表的是一位耄耋老人,如果让他当刘翔的陪练,那就直接光荣了。如何避免这样的情况呢,我们可以在Run方法中加一段逻辑代码,先判断Age大小,如果小于2或大于60直接抛异常,但是2-60岁之间也得用Switch来分年龄阶段地判断speed参数是否合适,那么逻辑就相当臃肿。简而言之,如何用特性表示一个方法不能被使用呢?OK, here we go:

   1: [Obsolete("I'm so old, don't kill me!", true)]
   2: public virtual void Run(int speed)
   3: {
   4:     // Running is good for health.
   5: }

上面大致介绍了一下特性的使用与作用,接下来我们要向大家展示的是如何通过自定义特性来提高程序的灵活性,如果特性机制仅仅能使用.NET提供的那几种特性,不就太不过瘾了么。

首先,特性也是类。不同于其它类的是,特性都必须继承自System.Attribute类,否则编译器如何知道谁是特性谁是普通类呢。当编译器检测到一个类是特性的时候,它会识别出其中的信息并存放在元数据当中,仅此而已,编译器并不关心特性说了些什么,特性也不会对编译器起到任何作用,正如航空公司并不关心每个箱子要去哪里,只有箱子的主人和搬运工才会去关心这些细节。假设我们现在就是航空公司的管理人员,需要设计出前面提到的登机牌,那么很简单,我们先看看最主要的信息有哪些:

   1: public class BoardingCheckAttribute : Attribute
   2: {
   3:     public string ID { get; private set; }
   4:     public string Name { get; private set; }
   5:     public int FlightNumber { get; private set; }
   6:     public int PostionNumber { get; private set; }
   7:     public string Departure { get; private set; }
   8:     public string Destination { get; private set; }
   9: }

我们简单列举这些属性作为航空公司登机牌上的信息,用法和前面的一样,贴到HumanBase上就行了,说明此人具备登机资格。这里要简单提一下,你可能已经注意到了,在使用BoardingCheckAttribute的时候已经把Attribute省略掉了,不用担心,这样做是对的,因为编译器默认会自己加上然后查找这个属性类的。哦,等一下,我突然想起来他该登哪架飞机呢?显然,在这种需求下,我们的特性还没有起到应有的作用,我们还的做点儿工作,否则乘客面对一张空白的机票一定会很迷茫。

于是,我们必须给这个特性加上构造函数,因为它不仅仅表示登机的资格,还必须包含一些必要的信息才行:

   1: public BoardingCheckAttribute(string id, string name, string flightNumber, string positionNumber, string departure, string destination)
   2: {
   3:     this.ID = id;
   4:     this.Name = name;
   5:     this.FlightNumber = flightNumber;
   6:     this.PositionNumber = positionNumber;
   7:     this.Departure = departure;
   8:     this.Destination = destination;
   9: }

OK,我们的乘客就可以拿到一张正式的登机牌登机了,have a good flight!

   1: static void Main(string[] args)
   2: {
   3:     BoardingCheckAttribute boardingCheck = null;
   4:     object[] customAttributes = typeof(HumanPropertyBase).GetCustomAttributes(true);
   5:  
   6:     foreach (var attribute in customAttributes)
   7:     {
   8:         if (attribute is BoardingCheckAttribute)
   9:         {
  10:             boardingCheck = attribute as BoardingCheckAttribute;
  11:  
  12:             Console.WriteLine(boardingCheck.Name
  13:                         + "'s ID is "
  14:                         + boardingCheck.ID
  15:                         + ", he/she wants to "
  16:                         + boardingCheck.Destination
  17:                         + " from "
  18:                         + boardingCheck.Departure
  19:                         + " by the plane "
  20:                         + boardingCheck.FlightNumber
  21:                         + ", his/her position is "
  22:                         + boardingCheck.PositionNumber
  23:                         + ".");
  24:         }
  25:     }
  26:  
  27:     Console.ReadLine();
  28: }

Boeing Cabin

 

2.接口

 

接口是体现面向对象编程思想优越性的一件利器,为什么这么说呢?首先我们来看,接口是为继承而存在的,如果没有继承,那就自然不需要接口了,既然有继承,那就需要把可能被多个类所继承的一些公共部分抽象出来,接口封装的就是这些公共的行为规范(方法定义),类可以通过继承多个接口来丰富自己的行为机制,但是在C#中,类是不可以继承多个类的。C#最显著的特点就是极大地提高了程序的开发效率和维护效率,所以对于继承多个类这种容易引起二义性的机制是深恶痛绝的。

我们都是由学生时代一路走过来的,所以我想借用学生时代的那些行为规范来作为现实生活中的接口的例子。

读小学的时候,我们至少会有两套行为规范:小学生行为规范和少先队员行为规范。首先我们都得遵守小学生行为规范,其次如果是少先队员,就还得遵守少先队员行为规范。我们对于这两套行为规范不列举太多细节规则,每套一条就够了,首先,作为少先队员,过马路时要先看红绿灯:

   1: interface IPupilRule
   2: {
   3:     void CrossTheRoad(int trafficLightIndex);
   4: }

在IPupileRule中,我们定义了“过马路看红绿灯”这样的行为规范,它其实就是一个函数声明,定义了函数名、返回值类型以及参数类型等信息,但是并没有函数体。对,接口中只能有函数定义这样的指导性原则,不允许存在函数体,至于具体的实现细节,那就“具体问题具体分析”吧。接下来我们再来定义少先队员行为规范,也不列举太多细则,一条足矣,少先队员不能抽烟:

   1: interface IYoungPioneerRule
   2: {
   3:     void NoSmoke();
   4: }

同样的,只有函数声明没有实现细节,因为行为守则这样的东西只能告诉你作为一名光荣的少先队员是绝对不能抽烟的,它不会也不能面面俱到地告诉你假如你叔叔阿姨给你递烟应该如何谢绝,假如你爸爸教你抽烟应该如何拒绝,另外假如那些辍学的坏孩子强迫你抽烟又该如何反抗等等。废话不多说,否则真成了“提供函数体的接口”了。

既然接口是不提供函数实现细节的,那么当一个小学生需要横过马路的时候,就只能靠他自己来完成具体的逻辑实现了:

   1: public class Pupil : IPupilRule
   2: {
   3:     /// <summary>
   4:     /// Cross the road and notice the traffic light.
   5:     /// </summary>
   6:     /// <param name="trafficLightIndex">The index of traffic light.</param>
   7:     public void CrossTheRoad(int trafficLightIndex)
   8:     {
   9:         switch (trafficLightIndex)
  10:         {
  11:             case 0:     // Red, stop.
  12:                 break;
  13:             case 1:     // Yellow, stop.
  14:                 break;
  15:             case 2:     // Green, go.
  16:                 break;
  17:             default:    // Unknown situation, thinking.
  18:                 break;
  19:         }
  20:     }
  21: }

接下来,少先队员出场了。首先我们来分析一下这个场景,少先队员是需要遵守少先队员行为规范的小学生。由此可见,少先队员需要实现前面提到的两种行为规范中的所有规定,这也就体现了接口的好处,可以实现多重继承。当然,在本文所用的例子当中,少先队员大可不必重新继承并实现IPupilRule接口了,他既然是一名小学生,那就可以继承Pupil这个类,而且他并不需要改变Pupil中对小学生行为规范的具体实现细节,他只需要自己实现少先队员行为规范中的规定就行了。

   1: public class YoungPioneer : Pupil, IYoungPioneerRule
   2: {
   3:     /// <summary>
   4:     /// Young pioneer should help other people.
   5:     /// </summary>
   6:     public void NoSmoke()
   7:     {
   8:         throw new NotImplementedException("I can't smoke because I'm a young pioneer.");
   9:     }
  10: }

另外,接口也是可以继承接口的,并且可以多重继承。我们假设有一个向阳小学,这个学校还有自己的校规,校规内容不多,主要是要求学生要严格遵守小学生行为规范和少先队员行为规范,并且都要会唱校歌。

   1: interface IXiangYangSchoolRule : IPupilRule, IYoungPioneerRule
   2: {
   3:     void SingSchoolSong();
   4: }

因此这套校规编写起来倒不麻烦,继承了两套现成的行为规范并且增加一点儿自己的要求就行了。向阳小学的每一位学生都得遵守这套校规:

   1: public class XiangYangPupil : IXiangYangSchoolRule
   2: {
   3:     public void SingSchoolSong()
   4:     {
   5:         Console.WriteLine("I love Xiang Yang School!");
   6:     }
   7:  
   8:     public void CrossTheRoad(int trafficLightIndex)
   9:     {
  10:         throw new NotImplementedException();
  11:     }
  12:  
  13:     public void NoSmoke()
  14:     {
  15:         throw new NotImplementedException();
  16:     }
  17: }

通过上面的介绍,您大概已经对如何使用接口有了一些初步的认识,当面对实际问题的时候,只要合理地分析和设计,接口一定会为我们的开发工作带来很大的便利!

Pupil

3.泛型

泛型是C# 2.0版本才开始有的语言特性,不过“泛型”这个概念并不是最先出现在编程领域的,例如C++中的模板。

List<T>就是一个泛型应用。你可以在需要时声明一个强类型的List<T>实例,然后随意地往里面添加、删除和查询同一类型的元素。泛型就是一个非常方便的数据结构,长期使用C#的朋友大多都常常用到泛型。本文就简单地通过创建自己的泛型类来介绍一下泛型,希望能够加深初学者对泛型(这个名字很奇怪的东西)的认识和理解。

用到泛型的时候你就会发现,泛型其实就像一个口袋,你可以很方便地往里面装东西,只是在第一次使用这个口袋的时候要注意声明它只能装什么样类型的东西,以后就不能装错了。那么我们就用钱包为例吧,我们首先描述一下钱包。钱包的用途不外乎是装点儿东西,当然,除了钱还可以装其它很多东西,例如银行卡、便签条、照片等等,但是这些东西有些共同的地方,至少是尺寸方面不能超过钱包的限制,谁可以把冰箱也揣在钱包里呢?因此,我们在设计能装进钱包的物品的类的时候就要考虑到尺寸的因素。

   1: public class WalletThingBase
   2: {
   3:     protected readonly int MaxLength = 10;
   4:     protected readonly int MaxWidth = 7;
   5:     protected readonly int MaxThickness = 1;
   6:  
   7:     private int _length = 0;
   8:     public int Length
   9:     {
  10:         get { return this._length; }
  11:         set
  12:         {
  13:             if (value <= 0 || value > this.MaxLength)
  14:             {
  15:                 throw new ArgumentException("Length is invalid.");
  16:             }
  17:  
  18:             this._length = value;
  19:         }
  20:     }
  21:  
  22:     private int _width = 0;
  23:     public int Width
  24:     {
  25:         get { return this._width; }
  26:         set
  27:         {
  28:             if (value <= 0 || value > this.MaxWidth)
  29:             {
  30:                 throw new ArgumentException("Width is invalid.");
  31:             }
  32:  
  33:             this._width = value;
  34:         }
  35:     }
  36:  
  37:     private int _thickness = 0;
  38:     public int Thickness
  39:     {
  40:         get { return this._thickness; }
  41:         set
  42:         {
  43:             if (value <= 0 || value > this.MaxThickness)
  44:             {
  45:                 throw new ArgumentException("Thickness is invalid.");
  46:             }
  47:  
  48:             this._thickness = value;
  49:         }
  50:     }
  51:  
  52:     public WalletThingBase(int length, int width, int thickness)
  53:     {
  54:         this.Length = length;
  55:         this.Width = width;
  56:         this.Thickness = thickness;
  57:     }
  58: }

接下来我们来派生几个类吧,银行卡以及信用卡:

   1: public class BankCard : WalletThingBase
   2: {
   3:     public int ID { get; set; }
   4:     public string Name { get; set; }
   5:     public string Password { get; set; }
   6:  
   7:     public BankCard(int length, int width, int thickness)
   8:         : base(length, width, thickness)
   9:     {
  10:     }
  11: }
  12:  
  13: public class CreditCard : BankCard
  14: {
  15:     public decimal Overdraft { get; set; }
  16:  
  17:     public CreditCard(int length, int width, int thickness)
  18:         : base(length, width, thickness)
  19:     {
  20:     }
  21: }

通过上面的代码可以看出,在创建派生自WalletThingBase类的所有类的时候,都会先检验其尺寸是否超标,如果是尺寸过大就不允许创建,也就表示你不能把它放进你的钱包。显然,银行卡尽管规格各异,但都是可以的。

接下来,我们就要来设计钱包这个类了。我们可以借助List<T>来简化我们的设计工作,最需要注意的其实就是严格把关,凡是非WallThingBase派生类都不允许进入,另外,还得简单提供一些放东西和取东西的函数,这样,一个基本的钱包就设计出来了。

   1: public class Wallet<T> : CollectionBase
   2: {
   3:     public Wallet()
   4:     {
   5:         Type baseType = typeof(T).BaseType;
   6:  
   7:         while (baseType != null
   8:             && baseType != typeof(Object)
   9:             && baseType.BaseType != typeof(Object))
  10:         {
  11:             baseType = baseType.BaseType;
  12:         }
  13:  
  14:         if (baseType != typeof(WalletThingBase))
  15:         {
  16:             throw new Exception(typeof(T).ToString() + " cannot be put into wallet.");
  17:         }
  18:     }
  19:  
  20:     public T this[int index]
  21:     {
  22:         get { return (T)List[index]; }
  23:         set { List[index] = value; }
  24:     }
  25:  
  26:     public int Add(T item)
  27:     {
  28:         return List.Add(item);
  29:     }
  30:  
  31:     public void Remove(T item)
  32:     {
  33:         List.Remove(item);
  34:     }
  35: }

泛型是一种很具有亲和力的语言特性,很容易让人接受也很容易让人喜欢上它,借助泛型的便利,尽情享受C#开发乐趣吧!

eWallet

4.多线程

 

多线程是程序员面试时常常会面对的问题,对多线程概念的掌握和理解水平,也会被一些老鸟用来衡量一个人的编程实力的重要参考指标。不论是实际工作需要还是为了应付面试,掌握多线程都是程序员职业生涯中一个必须经过的环节。其实当你把“多线程”和你的“职业生涯”联系在一起考虑的时候,就会觉得“多线程”是多么的渺小,对,没有跨越不过的山。不过就算它很渺小,但也有可能改变你的人生轨迹。不用担心,如果你对多线程还不太熟悉,那么我们就一起来看看什么是多线程吧。

跟前几篇的风格一样,我会在开篇的时候举一个现实生活中的例子,通过这个例子来映射一些晦涩枯燥的计算机编程专业知识,在让读者朋友很好地理解理论概念的同时,又避免了阅读教科书时的枯燥感觉。这次我要举的例子是公司。不一定是IT公司,尽量和编程领域远一点儿吧,那就假设是一家搬家公司吧。

假如我们把公司看做是一个进程,那么人就是其中的线程。进程必须得有一个主线程,公司在创业初期往往可能出现一人打天下的现象,但是,至少得有一个人,公司才能运作。公司创业初期,业务还不算太多,往往就是老板一个人身兼数职,一天如果只有1、2趟活儿,应该还是忙得过来的。时间长了,随着业务的发展、口碑的建立,生意越来越兴隆,一个人肯定就忙不过来了。假设一天有5个活儿,老板一个人必须搬完A家才能搬B家,搬到黄昏估计也就搬到C家,D和E家都还在焦急地等待着呢。老板一个人要充当搬运工、司机、业务联系人、法人代表、出纳等众多角色,累死累活公司的规模也上不去,人手不够制约了公司的发展。那么怎么办,很简单,增加人手,用编程的话来说就是“再起个线程”。

我们现在就用代码来描述这样的场景吧,首先,我们准备成立一家搬家公司,于是要准备好将来和客户签的合同书:

   1: public class Contract
   2: {
   3:     public string ID { get; private set; }
   4:     public string From { get; set; }
   5:     public string To { get; set; }
   6:     public decimal Fee { get; set; }
   7:  
   8:     public Contract()
   9:     {
  10:         this.ID = Guid.NewGuid().ToString()
  11:     }
  12: }

简是简单了点儿,好歹也是份合同,现在我们就去申请注册一家公司,并组建好初创团队,哪怕目前还只有老板一个人:

   1: public class HouseMovingCompany
   2: {
   3:     private static HouseMovingCompany _instance = null;
   4:     public static HouseMovingCompany Instance
   5:     {
   6:         get { return (_instance == null ? _instance = new HouseMovingCompany() : _instance); }
   7:     }
   8:  
   9:     public List<Contract> Contracts { get; private set; }
  10:  
  11:     public HouseMovingCompany()
  12:     {
  13:         this.Contracts = new List<Contract>();
  14:     }
  15:  
  16:     public void MoveHouse()
  17:     {
  18:         if (this.Contracts == null || this.Contracts.Count == 0)
  19:         {
  20:             return;
  21:         }
  22:  
  23:         Contract contract = contract = this.Contracts[0];
  24:         this.Contracts.RemoveAt(0);
  25:  
  26:         if (!String.IsNullOrEmpty(contract.From) && !String.IsNullOrEmpty(contract.To))
  27:         {
  28:             Console.WriteLine("Move the house from {0} to {1}.", contract.From, contract.To);
  29:         }
  30:  
  31:         Thread.Sleep(5000);
  32:     }
  33: }

好了,现在公司实体有了,老板就可以开始忙活了:

   1: static void Main(string[] args)
   2: {
   3:     HouseMovingCompany.Instance.Contracts.Add(new Contract { From = "WuDaokou", To = "LinDa Road", Fee = 500 });
   4:  
   5:     while (HouseMovingCompany.Instance.Contracts.Count > 0)
   6:     {
   7:         HouseMovingCompany.Instance.MoveHouse();
   8:     }
   9: }

我们在前面设置了每次搬家耗时5秒钟,咱们把它想象成5个小时。嗯,一天接一个单子,还可以接受,但是随着老板生意日渐兴隆,有时候一天要接3个单子,这就最少要工作15个小时了,还要操心公司的运营等问题,的确忙不过来了,而且照这样算,老板一天不可能完成5个或5个以上的单子,严重制约了公司的发展:

   1: static void Main(string[] args)
   2: {
   3:     HouseMovingCompany.Instance.Contracts.Add(new Contract { From = "WuDaokou", To = "LinDa Road", Fee = 500 });
   4:     HouseMovingCompany.Instance.Contracts.Add(new Contract { From = "XiDan", To = "WangFujing", Fee = 1000 });
   5:     HouseMovingCompany.Instance.Contracts.Add(new Contract { From = "XiangShan", To = "The Forbidden City", Fee = 10000 });
   6:  
   7:     while (HouseMovingCompany.Instance.Contracts.Count > 0)
   8:     {
   9:         HouseMovingCompany.Instance.MoveHouse();
  10:     }
  11: }

一天夜里,老板拖着疲倦的身子回到家里,一进门就一头倒在床上,他极力睁着快睁不开的眼睛,努力地对自己说:“不行,我一定要想个办法,不然我会被累死的!”。

其实办法很简单,谁都知道,招聘几个员工,再买几辆车,大家分头行动,不仅分担了工作负担,而且在规模扩大的同时还可以完成更多更大的单子。好,我们现在就借助多线程机制来实现我们的想法:

   1: static void Main(string[] args)
   2: {
   3:     HouseMovingCompany.Instance.Contracts.Add(new Contract { From = "WuDaokou", To = "LinDa Road", Fee = 500 });
   4:     HouseMovingCompany.Instance.Contracts.Add(new Contract { From = "XiDan", To = "WangFujing", Fee = 1000 });
   5:     HouseMovingCompany.Instance.Contracts.Add(new Contract { From = "XiangShan", To = "The Forbidden City", Fee = 10000 });
   6:  
   7:     Thread thread = null;
   8:  
   9:     while (HouseMovingCompany.Instance.Contracts.Count > 0)
  10:     {
  11:         thread = new Thread(new ThreadStart(HouseMovingCompany.Instance.MoveHouse));
  12:  
  13:         thread.Start();
  14:     }
  15: }

在这段程序中,我们分头行动,让每项搬家任务都由一个小团队去完成,结果我们发现,现在做三个单子的时间跟做一个单子的时间是一样的,提高了效率也扩大了公司规模。但是,既然引入了新的工作机制,我们在公司内部也不得不做一些小小的调整:

   1: public void MoveHouse()
   2: {
   3:     if (this.Contracts == null || this.Contracts.Count == 0)
   4:     {
   5:         return;
   6:     }
   7:  
   8:     Contract contract = null;
   9:  
  10:     lock (this.Contracts)
  11:     {
  12:         contract = this.Contracts[0];
  13:         this.Contracts.RemoveAt(0);
  14:     }
  15:  
  16:     if (!String.IsNullOrEmpty(contract.From) && !String.IsNullOrEmpty(contract.To))
  17:     {
  18:         Console.WriteLine("Move the house from {0} to {1}.", contract.From, contract.To);
  19:     }
  20:  
  21:     Thread.Sleep(5000);
  22: }

调整的只是MoveHouse这个方法内部的一些实现细节。公司接到的单子都保存在Contracts中,所以搬家的时候需要去拿一个单子然后根据单子上的信息来工作。原先我们只有一个线程在操作Contracts,倒也不觉得什么,现在有多个线程都在对Contracts中的元素进行存取,我们不得不提防一些意外发生。这就是在使用多线程的时候常常需要考虑的并发问题,所以我们用了lock关键字,当一个线程要操作Contracts时,它先把Contracts锁起来,其实就是声明一下:“现在我在操作它,你们谁都不要动,等我弄完了再说。”在lock块结束时被锁定的对象才会被解锁,其它的线程现在才可以去操作它。

有了多线程机制,你会发现程序可以在更短的时间内完成更多的事情。本文没有将多线程机制中的所有概念面面俱到地列举出来,但是已经向你展示了该如何使用多线程以及什么时候可以考虑使用多线程,其它的一些细节有待你去进一步探索,例如,你可以设置线程的优先级(假设逻辑上跟Fee挂钩,类似于‘加急’)等等。

掌握多线程机制,并让它使你的应用程序变得更加强悍吧。

House Moving Company

5.反射

反射是一种晚绑定,它可以被开发者用来设计出更具灵活性的代码,而代价则是花费更多的系统资源开销使得应用程序可以在运行时获取一些未知信息。

说白了,在编写代码的时候,开发者可能还不知道或不能确定一些对象的信息,于是把决定权交给代码本身,将来在需要的时候由代码自己去获取和判断这些信息并作出相应的反应。这样的方式固然可以使代码更加灵活,但在想要使用反射的时候,必须先考虑好在性能与灵活之间的一个权衡,不能盲目地因为C#提供了反射机制就一个劲儿地用,我们应该发扬John Carmack“榨干PC机3D图像性能”的精神,而不要因为硬件越来越便宜就让机器背负沉重的包袱去做一些意义不大的事情。

我们还是继续本系列一贯的风格,用生活中的例子来讲解什么是反射以及反射的基本使用方式。

我先问大家一个问题:“《集结号》中谷子地穿的南朝鲜军服从哪儿来?”显然不是临时找裁缝赶制的,肯定是从俘虏身上扒下来的。好,那我们就来补充一点儿《集结号》的镜头看看什么是反射。

一天傍晚,一名又饥又渴的南朝鲜掉队士兵在乡间小路上蹒跚地走着,他甚至都快扛不动身上的枪了,也不知道队伍在哪里,只好认准一个方向努力寻找。“不许动!”从路边草丛中跳出的两名埋伏着的中国士兵喝道,这名南朝鲜士兵本来就精疲力尽,哪受得了这番惊吓,当时就一屁股坐在地上了。中国士兵把他押回营部并向上级报告,他们在附近巡逻的时候意外俘获一名南朝鲜掉队士兵,听候处置。

这件事情被上报到王团长那里,王团长正拿着放大镜跟几名军官在地图前研究作战方案,听到这件事,不紧不慢地跟他身边的高连长说道:“高诚啊,你去看看,没啥大问题就送到后方战俘营去。”“是!”高连长接到命令便转身出去了。

高连长来到一件狭小的房间,那名南朝鲜士兵正半躺在地上,手里拿着中国士兵给他的馒头和水,一口一口地嚼着,看到高连长进来了,眼睛里流露出紧张和恐惧的神情。高连长简单介绍了中国方面善待俘虏的政策,便开始审问他——注意,反射开始了!

“哪儿的?”“联合国军李承晚系部队3团2排1班。”

“叫什么名字?”“思密达。”

“这次执行什么任务?”“潜入贵军阵地并指引炮兵进行射击。”

……

一番软硬兼施之后,高连长掌握了这个小兵所有的信息,整理好材料去跟王团长汇报去了,而这个小兵被带到一个小屋子里继续啃馒头去了,身上的行头也被换了下来,换上了专门为战俘准备的棉衣。

高连长一手拿着笔录材料,一手拎着南朝鲜士兵的全套行头去见了王团长,汇报之后王团长会心地笑了:“小高啊,不错,你父亲当初把你交给我,我就叫他放心,你是块好料子,怎么样,我没说错吧,呵呵,不过你也莫要骄傲,我们不能轻敌,这样,你带着这身行头去找谷子地,叫他换上之后连夜潜入敌方阵地,敌人不是想拿炮弹砸我们吗,那我们就用他们的方式,先发制人,让老谷指引我军炮火明日凌晨发起总攻。”

谷子地换上了这身洋行头,揣了半条烟就出发了,他潜入敌方阵地后冷静地掏出敌人的望远镜进行了周密地观察,并通过无线电给后方友军传递射击参数,成功地引导我军取得了这次重大胜利。

上面的故事情节描述得有点儿多了,赶紧来看代码吧,首先,我们得有个倒霉的南朝鲜士兵做引子:

   1: namespace UN
   2: {
   3:     internal class SouthKoreaArmy
   4:     {
   5:         public string Name { get; set; }
   6:  
   7:         public string Search(string destination)
   8:         {
   9:             return "OK, fire!";
  10:         }
  11:     }
  12: }

然后,我们用代码来实现谷子地所完成的行动:

   1: internal class ChineseArmy
   2: {
   3:     public static void Action(object soldier)
   4:     {
   5:         Type type = soldier.GetType();
   6:         object ziDiGu = Activator.CreateInstance(type);
   7:         MethodInfo methodInfo = type.GetMethod("Search");
   8:  
   9:         Console.WriteLine(methodInfo.Invoke(ziDiGu, new object[] { "Position" }));
  10:     }
  11: }

这段代码很好理解,第5行我们审问了被俘的南朝鲜士兵并得到了他提供的信息和服装,然后让老谷用他的服装化装成南朝鲜士兵。第7行,小样,你不是想侦查我吗,好,咱就来个以牙还牙,用你的方式来搞定你!接下来,谷子地潜入地方阵地并引导了我军发起总攻。

这里展示了反射的一些基本应用,例如获取类型信息、利用获取的类型动态生成对象,并动态调用其方法。当然,反射机制能做的事情不仅仅是这几样,不过目标都一样,由程序自己去获取信息、做出反应。在不用担心性能开销的情况下,反射可以使你的程序更加灵活强大!

Assembly

6.委托与事件

 

我们出去吃饭,总是喜欢去人多生意好的馆子,因为这样的馆子往往味道和服务都比较好(相信群众),而那些生意冷清的馆子往往无人问津。生意好的馆子固然有其长处,但去这样地方就餐又总是需要先排队等位置,所以排号是比较流行的方式。当然,如果这家馆子的座位充足,就不需要排号,但是上菜又比较慢。无论怎样,如果厨房一时半会儿无法做好你的菜,那么你就只好耐心地等待,在这个时候你可以做自己的事情,跟朋友聊天、玩手机或者看美女,没有人会傻傻地站在厨房门口目不转睛地盯着厨师手中的锅铲直到做好自己的菜。

在软件开发工作中,我们也常常会遇到类似的问题,我们向某个服务器(或类似于服务器的东西)发送一个请求,需要获取到反馈信息后才能继续某项工作,但是如果这项工作不是最重要的或者目前唯一的工作项,我们完全没有必要让这个等待过程阻碍整个软件的使用,如果这样做了,那将获得非常糟糕的用户体验,就像我们看到有人一动不动地站在厨房门口等菜一样。

C#提供了委托机制来实现异步处理,也就是说,你向服务器发送请求以后就可以把精力用在做别的事情上,服务器返回请求后应用程序会自动调用你之前安排好的方法来处理接下来的工作。换句话说,你点好了菜,接下来就可以和朋友聊天,厨房做好了菜无论是叫号还是由服务员端到你桌上来,反正不用你再操心了。

下面我们先创建一个顾客类:

   1: public class Customer
   2: {
   3:     public int ID { get; private set; }
   4:  
   5:     public Customer(int id)
   6:     {
   7:         this.ID = id;
   8:     }
   9: }
在这个场景中,我们不需要赋予Customer类太多属性,我们假设这家餐馆采用的方式是叫号而不是由服务员把菜端到每个顾客桌上,所以Customer类只要有个ID号就够了。

接下来我们来实现这个叫号的方法,虽然很简单,但是肯定不是只有一家餐馆采用这种方式,因此我们把这个方法放在单独的一个类里面:

   1: public class DelegateDemo
   2: {
   3:     public static void CallCustomer(Customer customer)
   4:     {
   5:         if (customer != null)
   6:         {
   7:             Console.WriteLine("Customer ID: " + customer.ID);
   8:         }
   9:     }
  10: }

然后我们来创建最重要的类,餐厅类:

   1: public class SampleRestaurant
   2: {
   3:     public List<Customer> Customers { get; private set; }
   4:  
   5:     public SampleRestaurant()
   6:     {
   7:         this.Customers = new List<Customer>();
   8:  
   9:         for (int index = 1; index <= 10; index++)
  10:         {
  11:             Customers.Add(new Customer(index));
  12:         }
  13:     }
  14:  
  15:     public delegate void EnumCustomerCallback(Customer customer);
  16:  
  17:     public void EnumCustomers(EnumCustomerCallback callback)
  18:     {
  19:         foreach (var customer in Customers)
  20:         {
  21:             callback(customer);
  22:         }
  23:     }
  24: }

在这个餐厅类里面,有一个顾客的集合,表示当前餐厅里所有的顾客,我们先往这个集合里面塞10个顾客进去以备待会儿叫号。然后我们在这个类里面声明了一个委托EnumCustomerCallback,并限定了它的函数签名。要使用这个委托的函数就必须符合该委托的签名形式。例如,该餐馆采用叫号的方式,也就是说我们要把DelegateDemo.CallCustomers方法传递给EnumCustomerCallback委托,因此它俩的函数签名是一致的。然后我们还定义了一个EnumCustomers方法,该方法的以EnumCustomerCallback委托为参数。该方法执行的内容是挨个遍历当前所有顾客,至于遍历到每个顾客是采取什么样的方式,叫号还是有服务员端菜呢,取决于传递给当前委托的方法来执行。

好,接下来我们来执行这段代码看看效果:

   1: static void Main(string[] args)
   2: {
   3:     SampleRestaurant restaurant = new SampleRestaurant();
   4:     SampleRestaurant.EnumCustomerCallback callCustomer
   5:         = new SampleRestaurant.EnumCustomerCallback(DelegateDemo.CallCustomer);
   6:     restaurant.EnumCustomers(callCustomer);
   7:  
   8:     Console.ReadLine();
   9: }

在这段代码的第4行,我们创建了一个委托实例callCustomers,并把叫号的方法DelegateDemo.CallCustomers传递给它座位该餐馆上菜的方式。然后我们再把这个方式传递给该餐厅的遍历顾客的方法,让它在遍历到每个顾客的时候采用老板设定好了的方式去执行。

下面的截图就是以上代码执行的结果,喇叭里挨个叫出顾客的编号,通知他们去取餐:

Delegate Demo Result

接下来我们来介绍一下事件机制,之所以把事件放在委托后面讲,是因为事件是在委托的基础上实现的。这一次我们站在厨师的角度来看待什么是事件。

作为一个厨师,炒菜是最基本的工作内容,而对于餐厅的服务员来说,可以拿着点菜单去叫厨师来炒菜,因此这就存在一个观察者模式,即发布和订阅的机制。服务员发布说:“顾客要个青椒肉丝”,厨师订阅说:“行,我马上炒个青椒肉丝”。定义事件需要注意的是,事件需要两个参数,一是引发该事件的对象,本例中是负责点菜的服务员;二是事件消息对象。其中,事件消息对象必须派生自System.EventArgs类。

因此我们先来设计一个点菜单作为事件消息类:

   1: public class OrderEventArgs : EventArgs
   2: {
   3:     public string DishName { get; set; }
   4:     public int Amount { get; set; }
   5: }

在这个点菜单中,我们只包含了菜名和数量,因为对于厨师来说,他只要知道这两个基本信息就够了。接下来我们来定义这个厨师类:

   1: public class Cook
   2: {
   3:     public Waitress TheWaitress { get; private set; }
   4:  
   5:     public Cook(Waitress waitress)
   6:     {
   7:         this.TheWaitress = waitress;
   8:         this.TheWaitress.OnOrderHandler += new Waitress.OrderEventHandler(waitress_OnOrderHandler);
   9:     }
  10:  
  11:     void waitress_OnOrderHandler(object sender, OrderEventArgs e)
  12:     {
  13:         Console.WriteLine("I cook " + e.Amount + " " + e.DishName + ".");
  14:     }
  15: }

在厨师类中包含了一个Waitress对象,这表示发布者,也就是叫厨师炒菜的服务员。第8行,厨师把自己添加进预订者列表,也就是说在服务员下单子的时候,厨房里得有现成的厨师才行,如果厨房里没有厨师,服务员发布了点菜事件也没人接招。

然后我们来看看事件的发布者,服务员:

   1: public class Waitress
   2: {
   3:     public delegate void OrderEventHandler(object sender, OrderEventArgs e);
   4:     public event OrderEventHandler OnOrderHandler;
   5:  
   6:     public void Order(string dishName, int amount)
   7:     {
   8:         if (String.IsNullOrEmpty(dishName) || amount <= 0)
   9:         {
  10:             return;
  11:         }
  12:  
  13:         OrderEventArgs orderEventArgs = new OrderEventArgs { DishName = dishName, Amount = amount };
  14:  
  15:         if (OnOrderHandler != null)
  16:         {
  17:             OnOrderHandler(this, orderEventArgs);
  18:         }
  19:     }
  20: }

在这个服务员类里面,我们首先定义了一个带有两个参数的委托以及一个事件。委托的两个参数一个是发布者对象,另一个是事件信息对象。然后,服务员类还提供了一个点餐方法,点餐方法创建一个事件信息类(点菜单),如果事件的订阅者不为空,也就是说厨房里目前有厨师,那么服务员就会把这个点菜单发送过去,而事件的订阅者会有专门的方法来处理这个事件。

   1: static void Main(string[] args)
   2: {
   3:     Waitress waitress = new Waitress();
   4:     Cook cook = new Cook(waitress);
   5:  
   6:     waitress.Order("QingJiaoRouSi", 1);
   7:  
   8:     Console.ReadLine();
   9: }

Restaurant

转自:http://www.cnblogs.com/Autumoon/archive/2008/04/16/1156864.html

原文地址:https://www.cnblogs.com/vicsmb/p/1784579.html