2 C# 高级篇

特性

特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。您可以通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所应用的元素前面的方括号([ ])来描述的。

特性(Attribute)用于添加元数据,如编译器指令和注释、描述、方法、类等其他信息。.Net 框架提供了两种类型的特性:预定义特性和自定义特性。

列举特性

列举特性的语法如下:

  1. [attribute(positional_parameters, name_parameter = value, ...)]
  2. element

特性的名称和值都是在方括号内规定的,放在这个特性应用的元素之前。表示位置的参数规定出特性的基本信息,名称参数规定了可选择的信息。

预定义特性

.Net Framework 提供了三种预定义的特性:

  • AttributeUsage
  • Conditional
  • Obsolete

AttributeUsage

该特性描述了用户定义的特性类是如何使用的。它规定了某个特性应用的项目类型。

规定这种特性的语法如下:

  1. [AttributeUsage(
  2. validon,
  3. AllowMultiple=allowmultiple,
  4. Inherited=inherited
  5. )]

其中,

  • 参数 validon 规定特性了能承载特性的语言元素。它是枚举器 AttributeTargets 的值的组合。默认值是 AttributeTargets.All。
  • 参数 allowmultiple(可选的)为该特性的 AllowMultiple 属性提供了一个布尔值。如果为 true,则该特性是多用的。默认值是 false(单用的)。
  • 参数 inherited(可选的)为该特性的 Inherited 属性提供一个布尔值。如果为 true,则该特性可被派生类继承。默认值是 false(不可继承)。

例如:

  1. [AttributeUsage(AttributeTargets.Class |
  2. AttributeTargets.Constructor |
  3. AttributeTargets.Field |
  4. AttributeTargets.Method |
  5. AttributeTargets.Property,
  6. AllowMultiple = true)]

Conditional

这个预定义特性标记了一个条件方法,其执行依赖于特定的预处理标识符。

它会引起方法调用的条件编译,取决于指定的值,比如 Debug 或 Trace。例如,当调试代码时显示变量的值。

规定该特性的语法如下:

  1. [Conditional(
  2. conditionalSymbol
  3. )]

例如:

  1. [Conditional("DEBUG")]

下面的实例演示了该特性:

  1. #define DEBUG
  2. using System;
  3. using System.Diagnostics;
  4.  
  5. public class Myclass
  6. {
  7. [Conditional("DEBUG")]
  8. public static void Message(string msg)
  9. {
  10. Console.WriteLine(msg);
  11. }
  12. }
  13.  
  14. class Test
  15. {
  16. static void function1()
  17. {
  18. Myclass.Message("In Function 1.");
  19. function2();
  20. }
  21. static void function2()
  22. {
  23. Myclass.Message("In Function 2.");
  24. }
  25.  
  26. public static void Main()
  27. {
  28. Myclass.Message("In Main function.");
  29. function1();
  30. Console.ReadKey();
  31. }
  32. }

编译执行上述代码,得到如下结果:

  1. In Main function
  2. In Function 1
  3. In Function 2

Obsolete

这个预定义特性标记了不应被使用的程序实体。它可以让您通知编译器丢弃某个特定的目标元素。例如,当一个新方法被用在一个类中,但是您仍然想要保持类中的旧方法,您可以通过显示一个应该使用新方法,而不是旧方法的消息,来把它标记为 obsolete(过时的)。

规定该特性的语法如下:

  1. [Obsolete(
  2. message
  3. )]
  4. [Obsolete(
  5. message,
  6. iserror
  7. )]

其中,

  • 参数 message,是一个字符串,描述项目为什么过时的原因以及该替代使用什么。
  • 参数 iserror,是一个布尔值。如果该值为 true,编译器应把该项目的使用当作一个错误。默认值是 false(编译器生成一个警告)。

下面的实例演示了该特性:

  1. using System;
  2.  
  3. public class MyClass
  4. {
  5. [Obsolete("Don't use OldMethod, use NewMethod instead", true)]
  6. static void OldMethod()
  7. {
  8. Console.WriteLine("It is the old method");
  9. }
  10. static void NewMethod()
  11. {
  12. Console.WriteLine("It is the new method");
  13. }
  14. public static void Main()
  15. {
  16. OldMethod();
  17. }
  18. }

当你执行这个程序时,编译器会提示如下的错误:

  1. Don't use OldMethod, use NewMethod instead

创建自定义的特性

.Net 框架允许创建自定义特性,用于存储声明性的信息,且可在运行时被检索。该信息根据设计标准和应用程序需要,可与任何目标元素相关。

创建并使用自定义特性包含四个步骤:

  • 声明自定义特性
  • 构建自定义特性
  • 在目标程序元素上应用自定义特性
  • 通过反射访问特性

最后一个步骤包含编写一个简单的程序来读取元数据以便查找各种符号。元数据是用于描述其他数据的数据和信息。该程序应使用反射来在运行时访问特性。我们将在下一章详细讨论这点。

声明自定义特性

一个新的自定义特性应派生自 System.Attribute 类。例如:

  1. //一个自定义的特性BugFix被分配给类和类的成员
  2. [AttributeUsage(AttributeTargets.Class |
  3. AttributeTargets.Constructor |
  4. AttributeTargets.Field |
  5. AttributeTargets.Method |
  6. AttributeTargets.Property,
  7. AllowMultiple = true)]
  8.  
  9. public class DeBugInfo : System.Attribute

在上面的代码中,我们已经声明了一个名为 DeBugInfo 的自定义特性。

构建自定义特性

让我们构建一个名为 DeBugInfo 的自定义特性,该特性将存储调试程序获得的信息。它存储下面的信息:

  • bug 的代码编号
  • 辨认该 bug 的开发人员名字
  • 最后一次审查该代码的日期
  • 一个存储了开发人员标记的字符串消息

我们的 DeBugInfo 类将带有三个用于存储前三个信息的私有属性(property)和一个用于存储消息的公有属性(property)。所以 bug 编号、开发人员名字和审查日期将是 DeBugInfo 类的必需的定位( positional)参数,消息将是一个可选的命名(named)参数。

每个特性必须至少有一个构造函数。必需的定位( positional)参数应通过构造函数传递。下面的代码演示了 DeBugInfo 类:

  1. //一个自定义的特性BugFix被分配给类和类的成员
  2. [AttributeUsage(AttributeTargets.Class |
  3. AttributeTargets.Constructor |
  4. AttributeTargets.Field |
  5. AttributeTargets.Method |
  6. AttributeTargets.Property,
  7. AllowMultiple = true)]
  8.  
  9. public class DeBugInfo : System.Attribute
  10. {
  11. private int bugNo;
  12. private string developer;
  13. private string lastReview;
  14. public string message;
  15.  
  16. public DeBugInfo(int bg, string dev, string d)
  17. {
  18. this.bugNo = bg;
  19. this.developer = dev;
  20. this.lastReview = d;
  21. }
  22.  
  23. public int BugNo
  24. {
  25. get
  26. {
  27. return bugNo;
  28. }
  29. }
  30.  
  31. public string Developer
  32. {
  33. get
  34. {
  35. return developer;
  36. }
  37. }
  38.  
  39. public string LastReview
  40. {
  41. get
  42. {
  43. return lastReview;
  44. }
  45. }
  46.  
  47. public string Message
  48. {
  49. get
  50. {
  51. return message;
  52. }
  53. set
  54. {
  55. message = value;
  56. }
  57. }
  58. }

应用自定义特性

通过将特性放置在目标之前来使用它:

  1. [DeBugInfo(45, "Zara Ali", "12/8/2012", Message = "Return type mismatch")]
  2. [DeBugInfo(49, "Nuha Ali", "10/10/2012", Message = "Unused variable")]
  3. class Rectangle
  4. {
  5. //成员变量
  6. protected double length;
  7. protected double width;
  8. public Rectangle(double l, double w)
  9. {
  10. length = l;
  11. width = w;
  12. }
  13. [DeBugInfo(55, "Zara Ali", "19/10/2012", Message = "Return type mismatch")]
  14.  
  15. public double GetArea()
  16. {
  17. return length * width;
  18. }
  19. [DeBugInfo(56, "Zara Ali", "19/10/2012")]
  20.  
  21. public void Display()
  22. {
  23. Console.WriteLine("Length: {0}", length);
  24. Console.WriteLine("Width: {0}", width);
  25. Console.WriteLine("Area: {0}", GetArea());
  26. }
  27. }

在下一章节中,我们会介绍如何使用 Reflection 类来检索特性信息。

反射

反射(Reflection) 对象用于在运行时获取类型信息。该类位于 System.Reflection 命名空间中,可访问一个正在运行的程序的元数据。

System.Reflection 命名空间包含了允许您获取有关应用程序信息及向应用程序动态添加类型、值和对象的类。

反射的应用

反射(Reflection)有下列用途:

  • 它允许在运行时查看属性(attribute)信息。
  • 它允许审查集合中的各种类型,以及实例化这些类型。
  • 它允许延迟绑定的方法和属性(property)。
  • 它允许在运行时创建新类型,然后使用这些类型执行一些任务。

查看元数据

我们已经在上面的章节中提到过,使用反射(Reflection)可以查看属性(attribute)信息。

System.Reflection 类的 MemberInfo 对象需要被初始化,用于发现与类相关的属性(attribute)。为了做到这点,您可以定义目标类的一个对象,如下:

  1. System.Reflection.MemberInfo info = typeof(MyClass);

下面的程序示范了这点:

  1. using System;
  2.  
  3. [AttributeUsage(AttributeTargets.All)]
  4. public class HelpAttribute : System.Attribute
  5. {
  6. public readonly string Url;
  7.  
  8. public string Topic // Topic 是一个表示名字的参数
  9. {
  10. get
  11. {
  12. return topic;
  13. }
  14. set
  15. {
  16. topic = value;
  17. }
  18. }
  19.  
  20. public HelpAttribute(string url) // url 是一个表示位置的参数
  21. {
  22. this.Url = url;
  23. }
  24. private string topic;
  25. }
  26.  
  27. [HelpAttribute("Information on the class MyClass")]
  28. class MyClass
  29. {
  30. }
  31. namespace AttributeAppl
  32. {
  33. class Program
  34. {
  35. static void Main(string[] args)
  36. {
  37. System.Reflection.MemberInfo info = typeof(MyClass);
  38. object[] attributes = info.GetCustomAttributes(true);
  39. for (int i = 0; i < attributes.Length; i++)
  40. {
  41. System.Console.WriteLine(attributes[i]);
  42. }
  43.  
  44. Console.ReadKey();
  45. }
  46. }
  47. }

当上面的代码被编译和执行时,它会显示附加到类 MyClass 上的自定义属性:

  1. HelpAttribute

示例

在本实例中,我们将使用在上一章中创建的 DeBugInfo 属性,并使用反射(Reflection)来读取 Rectangle 类中的元数据。

  1. using System;
  2. using System.Reflection;
  3.  
  4. namespace BugFixApplication
  5. {
  6. //自定义特性BugFix分配给一个类和他的成员
  7. [AttributeUsage(AttributeTargets.Class |
  8. AttributeTargets.Constructor |
  9. AttributeTargets.Field |
  10. AttributeTargets.Method |
  11. AttributeTargets.Property,
  12. AllowMultiple = true)]
  13.  
  14. public class DeBugInfo : System.Attribute
  15. {
  16. private int bugNo;
  17. private string developer;
  18. private string lastReview;
  19. public string message;
  20.  
  21. public DeBugInfo(int bg, string dev, string d)
  22. {
  23. this.bugNo = bg;
  24. this.developer = dev;
  25. this.lastReview = d;
  26. }
  27.  
  28. public int BugNo
  29. {
  30. get
  31. {
  32. return bugNo;
  33. }
  34. }
  35.  
  36. public string Developer
  37. {
  38. get
  39. {
  40. return developer;
  41. }
  42. }
  43.  
  44. public string LastReview
  45. {
  46. get
  47. {
  48. return lastReview;
  49. }
  50. }
  51.  
  52. public string Message
  53. {
  54. get
  55. {
  56. return message;
  57. }
  58. set
  59. {
  60. message = value;
  61. }
  62. }
  63. }
  64. [DeBugInfo(45, "Zara Ali", "12/8/2012", Message = "Return type mismatch")]
  65. [DeBugInfo(49, "Nuha Ali", "10/10/2012", Message = "Unused variable")]
  66.  
  67. class Rectangle
  68. {
  69. //成员变量
  70. protected double length;
  71. protected double width;
  72. public Rectangle(double l, double w)
  73. {
  74. length = l;
  75. width = w;
  76. }
  77. [DeBugInfo(55, "Zara Ali", "19/10/2012", Message = "Return type mismatch")]
  78. public double GetArea()
  79. {
  80. return length * width;
  81. }
  82. [DeBugInfo(56, "Zara Ali", "19/10/2012")]
  83. public void Display()
  84. {
  85. Console.WriteLine("Length: {0}", length);
  86. Console.WriteLine("Width: {0}", width);
  87. Console.WriteLine("Area: {0}", GetArea());
  88. }
  89. }//Rectangle 类的结束
  90.  
  91. class ExecuteRectangle
  92. {
  93. static void Main(string[] args)
  94. {
  95. Rectangle r = new Rectangle(4.5, 7.5);
  96. r.Display();
  97. Type type = typeof(Rectangle);
  98.  
  99. //遍历 Rectangle 类的属性
  100. foreach (Object attributes in type.GetCustomAttributes(false))
  101. {
  102. DeBugInfo dbi = (DeBugInfo)attributes;
  103. if (null != dbi)
  104. {
  105. Console.WriteLine("Bug no: {0}", dbi.BugNo);
  106. Console.WriteLine("Developer: {0}", dbi.Developer);
  107. Console.WriteLine("Last Reviewed: {0}", dbi.LastReview);
  108. Console.WriteLine("Remarks: {0}", dbi.Message);
  109. }
  110. }
  111.  
  112. //遍历方法属性
  113. foreach (MethodInfo m in type.GetMethods())
  114. {
  115. foreach (Attribute a in m.GetCustomAttributes(true))
  116. {
  117. DeBugInfo dbi = (DeBugInfo)a;
  118. if (null != dbi)
  119. {
  120. Console.WriteLine("Bug no: {0}, for Method: {1}", dbi.BugNo, m.Name);
  121. Console.WriteLine("Developer: {0}", dbi.Developer);
  122. Console.WriteLine("Last Reviewed: {0}", dbi.LastReview);
  123. Console.WriteLine("Remarks: {0}", dbi.Message);
  124. }
  125. }
  126. }
  127.  
  128. Console.ReadLine();
  129. }
  130. }
  131. }

编译执行上述代码,得到如下结果:

  1. Length: 4.5
  2. Width: 7.5
  3. Area: 33.75
  4. Bug No: 49
  5. Developer: Nuha Ali
  6. Last Reviewed: 10/10/2012
  7. Remarks: Unused variable
  8. Bug No: 45
  9. Developer: Zara Ali
  10. Last Reviewed: 12/8/2012
  11. Remarks: Return type mismatch
  12. Bug No: 55, for Method: GetArea
  13. Developer: Zara Ali
  14. Last Reviewed: 19/10/2012
  15. Remarks: Return type mismatch
  16. Bug No: 56, for Method: Display
  17. Developer: Zara Ali
  18. Last Reviewed: 19/10/2012
  19. Remarks:

属性

属性是类、结构体和接口的命名成员。类或结构体中的成员变量或方法称为域。属性是域的扩展,且可使用相同的语法来访问。它们使用访问器让私有域的值可被读写或操作。

属性不会确定存储位置。相反,它们具有可读写或计算它们值的访问器。

例如,有一个名为 Student 的类,带有 age、name 和 code 的私有域。我们不能在类的范围以外直接访问这些域,但是我们可以拥有访问这些私有域的属性。

访问器

属性的访问器包含有助于获取(读取或计算)或设置(写入)属性的可执行语句。访问器声明可包含一个 get 访问器、一个 set 访问器,或者同时包含二者。例如:

  1. // 为字符类型声明一个叫 Code 的属性:
  2. public string Code
  3. {
  4. get
  5. {
  6. return code;
  7. }
  8. set
  9. {
  10. code = value;
  11. }
  12. }
  13.  
  14. // 为字符类型声明一个叫 Name 的属性:
  15. public string Name
  16. {
  17. get
  18. {
  19. return name;
  20. }
  21. set
  22. {
  23. name = value;
  24. }
  25. }
  26.  
  27. // 为整形类型声明一个叫 Age 的属性:
  28. public int Age
  29. {
  30. get
  31. {
  32. return age;
  33. }
  34. set
  35. {
  36. age = value;
  37. }
  38. }

示例

下面的程序说明了特性是如何使用的:

  1. using System;
  2. namespace tutorialspoint
  3. {
  4. class Student
  5. {
  6. private string code = "N.A";
  7. private string name = "not known";
  8. private int age = 0;
  9.  
  10. // 为字符类型声明一个叫 Code 的属性:
  11. public string Code
  12. {
  13. get
  14. {
  15. return code;
  16. }
  17. set
  18. {
  19. code = value;
  20. }
  21. }
  22.  
  23. // 为字符类型声明一个叫 Name 的属性:
  24. public string Name
  25. {
  26. get
  27. {
  28. return name;
  29. }
  30. set
  31. {
  32. name = value;
  33. }
  34. }
  35.  
  36. // 为整形类型声明一个叫 Age 的属性:
  37. public int Age
  38. {
  39. get
  40. {
  41. return age;
  42. }
  43. set
  44. {
  45. age = value;
  46. }
  47. }
  48. public override string ToString()
  49. {
  50. return "Code = " + Code +", Name = " + Name + ", Age = " + Age;
  51. }
  52. }
  53.  
  54. class ExampleDemo
  55. {
  56. public static void Main()
  57. {
  58.  
  59. // 创建一个 Student 类的对象
  60. Student s = new Student();
  61.  
  62. // 为 student 对象设置 code,name 和 age
  63. s.Code = "001";
  64. s.Name = "Zara";
  65. s.Age = 9;
  66. Console.WriteLine("Student Info: {0}", s);
  67.  
  68. //为 age 加 1
  69. s.Age += 1;
  70. Console.WriteLine("Student Info: {0}", s);
  71. Console.ReadKey();
  72. }
  73. }
  74. }

编译执行上述代码,得到如下结果:

  1. Student Info: Code = 001, Name = Zara, Age = 9
  2. Student Info: Code = 001, Name = Zara, Age = 10

抽象属性

抽象类可拥有抽象属性,这些属性应在派生类中被实现。下面的程序说明了这点:

  1. using System;
  2. namespace tutorialspoint
  3. {
  4. public abstract class Person
  5. {
  6. public abstract string Name
  7. {
  8. get;
  9. set;
  10. }
  11. public abstract int Age
  12. {
  13. get;
  14. set;
  15. }
  16. }
  17.  
  18. class Student : Person
  19. {
  20. private string code = "N.A";
  21. private string name = "N.A";
  22. private int age = 0;
  23.  
  24. // 为字符类型声明一个叫 Code 的属性:
  25. public string Code
  26. {
  27. get
  28. {
  29. return code;
  30. }
  31. set
  32. {
  33. code = value;
  34. }
  35. }
  36.  
  37. // 为字符类型声明一个叫 Name 的属性:
  38. public override string Name
  39. {
  40. get
  41. {
  42. return name;
  43. }
  44. set
  45. {
  46. name = value;
  47. }
  48. }
  49.  
  50. // 为整形类型声明一个叫 Age 的属性:
  51. public override int Age
  52. {
  53. get
  54. {
  55. return age;
  56. }
  57. set
  58. {
  59. age = value;
  60. }
  61. }
  62. public override string ToString()
  63. {
  64. return "Code = " + Code +", Name = " + Name + ", Age = " + Age;
  65. }
  66. }
  67.  
  68. class ExampleDemo
  69. {
  70. public static void Main()
  71. {
  72. // 创建一个 Student 类的对象
  73. Student s = new Student();
  74.  
  75. // 为 student 对象设置 code,name 和 age
  76. s.Code = "001";
  77. s.Name = "Zara";
  78. s.Age = 9;
  79. Console.WriteLine("Student Info:- {0}", s);
  80.  
  81. //为age加1
  82. s.Age += 1;
  83. Console.WriteLine("Student Info:- {0}", s);
  84. Console.ReadKey();
  85. }
  86. }
  87. }

编译执行上述代码,得到如下结果:

  1. Student Info: Code = 001, Name = Zara, Age = 9
  2. Student Info: Code = 001, Name = Zara, Age = 10

索引器

创建索引器可以使一个对象像数组一样被索引。为类定义索引器时,该类的行为类似于一个虚拟数组,使用数组访问运算符([ ])则可以对该类来进行访问。

句法规则

创建一个一维索引器的规则如下:

  1. element-type this[int index]
  2. {
  3. // get 访问器
  4. get
  5. {
  6. // 返回 index 指定的值
  7. }
  8.  
  9. // set 访问器
  10. set
  11. {
  12. // 设置 index 指定的值
  13. }
  14. }

使用索引器

索引器的声明在某种程度上类似于属性的声明,例如,使用 getset 方法来定义一个索引器。不同的是,属性值的定义要求返回或设置一个特定的数据成员,而索引器的定义要求返回或设置的是某个对象实例的一个值,即索引器将实例数据切分成许多部分,然后通过一些方法去索引、获取或是设置每个部分。

定义属性需要提供属性名,而定义索引器需要提供一个指向对象实例的 this 关键字。

示例:

  1. using System;
  2. namespace IndexerApplication
  3. {
  4. class IndexedNames
  5. {
  6. private string[] namelist = new string[size];
  7. static public int size = 10;
  8. public IndexedNames()
  9. {
  10. for (int i = 0; i < size; i++)
  11. namelist[i] = "N. A.";
  12. }
  13.  
  14. public string this[int index]
  15. {
  16. get
  17. {
  18. string tmp;
  19.  
  20. if( index >= 0 && index <= size-1 )
  21. {
  22. tmp = namelist[index];
  23. }
  24. else
  25. {
  26. tmp = "";
  27. }
  28.  
  29. return ( tmp );
  30. }
  31. set
  32. {
  33. if( index >= 0 && index <= size-1 )
  34. {
  35. namelist[index] = value;
  36. }
  37. }
  38. }
  39.  
  40. static void Main(string[] args)
  41. {
  42. IndexedNames names = new IndexedNames();
  43. names[0] = "Zara";
  44. names[1] = "Riz";
  45. names[2] = "Nuha";
  46. names[3] = "Asif";
  47. names[4] = "Davinder";
  48. names[5] = "Sunil";
  49. names[6] = "Rubic";
  50. for ( int i = 0; i < IndexedNames.size; i++ )
  51. {
  52. Console.WriteLine(names[i]);
  53. }
  54.  
  55. Console.ReadKey();
  56. }
  57. }
  58. }

编译执行上述代码,得到如下结果:

  1. Zara
  2. Riz
  3. Nuha
  4. Asif
  5. Davinder
  6. Sunil
  7. Rubic
  8. N. A.
  9. N. A.
  10. N. A.

重载索引器

索引器允许重载,即允许使用多种不同类型的参数来声明索引器。索引值可以是整数,但也可以是其他的数据类型,如字符型。

重载索引器示例:

  1. using System;
  2. namespace IndexerApplication
  3. {
  4. class IndexedNames
  5. {
  6. private string[] namelist = new string[size];
  7. static public int size = 10;
  8. public IndexedNames()
  9. {
  10. for (int i = 0; i < size; i++)
  11. {
  12. namelist[i] = "N. A.";
  13. }
  14. }
  15.  
  16. public string this[int index]
  17. {
  18. get
  19. {
  20. string tmp;
  21.  
  22. if( index >= 0 && index <= size-1 )
  23. {
  24. tmp = namelist[index];
  25. }
  26. else
  27. {
  28. tmp = "";
  29. }
  30.  
  31. return ( tmp );
  32. }
  33. set
  34. {
  35. if( index >= 0 && index <= size-1 )
  36. {
  37. namelist[index] = value;
  38. }
  39. }
  40. }
  41. public int this[string name]
  42. {
  43. get
  44. {
  45. int index = 0;
  46. while(index < size)
  47. {
  48. if (namelist[index] == name)
  49. {
  50. return index;
  51. }
  52. index++;
  53. }
  54. return index;
  55. }
  56.  
  57. }
  58.  
  59. static void Main(string[] args)
  60. {
  61. IndexedNames names = new IndexedNames();
  62. names[0] = "Zara";
  63. names[1] = "Riz";
  64. names[2] = "Nuha";
  65. names[3] = "Asif";
  66. names[4] = "Davinder";
  67. names[5] = "Sunil";
  68. names[6] = "Rubic";
  69.  
  70. // 使用带有 int 参数的第一个索引器
  71. for (int i = 0; i < IndexedNames.size; i++)
  72. {
  73. Console.WriteLine(names[i]);
  74. }
  75.  
  76. // 使用带有 string 参数的第二个索引器
  77. Console.WriteLine(names["Nuha"]);
  78. Console.ReadKey();
  79. }
  80. }
  81. }

编译执行上述代码,得到如下结果:

  1. Zara
  2. Riz
  3. Nuha
  4. Asif
  5. Davinder
  6. Sunil
  7. Rubic
  8. N. A.
  9. N. A.
  10. N. A.
  11. 2

委托

C# 中的委托类似于 C 或 C++ 中指向函数的指针。委托表示引用某个方法的引用类型变量,运行时可以更改引用对象。

特别地,委托可以用于处理事件或回调函数。并且,所有的委托类都是从 System.Delegate 类继承而来。

声明委托

声明委托时,需要定义能够被委托所引用的方法,任意委托可以引用与该委托拥有相同签名的方法。如:

  1. public delegate int MyDelegate (string s);

上述委托可以用于引用任何一个以字符型为参数的方法,且返回值类型为整型。

声明委托的句法规则为:

  1. delegate <return type> <delegate-name> <parameter list>

实例化委托

声明委托之后,必须使用 new 关键字和一个特定的方法来创建一个委托对象。创建时,传递到 new 语句的参数写法与方法调用相同,但是不带有参数,例如:

  1. public delegate void printString(string s);
  2. ...
  3. printString ps1 = new printString(WriteToScreen);
  4. printString ps2 = new printString(WriteToFile);

下述示例演示了委托的声明、实例化,此处的委托用于引用一个带有一个整型参数的方法,且该方法返回一个整型值。

  1. using System;
  2.  
  3. delegate int NumberChanger(int n);
  4. namespace DelegateAppl
  5. {
  6. class TestDelegate
  7. {
  8. static int num = 10;
  9. public static int AddNum(int p)
  10. {
  11. num += p;
  12. return num;
  13. }
  14.  
  15. public static int MultNum(int q)
  16. {
  17. num *= q;
  18. return num;
  19. }
  20. public static int getNum()
  21. {
  22. return num;
  23. }
  24.  
  25. static void Main(string[] args)
  26. {
  27. // 创建委托实例
  28. NumberChanger nc1 = new NumberChanger(AddNum);
  29. NumberChanger nc2 = new NumberChanger(MultNum);
  30.  
  31. // 使用委托对象调用方法
  32. nc1(25);
  33. Console.WriteLine("Value of Num: {0}", getNum());
  34. nc2(5);
  35. Console.WriteLine("Value of Num: {0}", getNum());
  36. Console.ReadKey();
  37. }
  38. }
  39. }

编译执行上述代码,得到如下结果:

  1. Value of Num: 35
  2. Value of Num: 175

委托的多播

委托对象可通过 "+" 运算符进行合并。一个合并委托可以调用它所合并的两个委托,但只有相同类型的委托可被合并。"-" 运算符则可用于从合并的委托中移除其中一个委托。

利用委托的这种特性,可以创建一个委托被调用时所涉及的方法的调用列表。这被称为委托的多播,也叫组播。下面的程序演示了委托的多播:

  1. using System;
  2.  
  3. delegate int NumberChanger(int n);
  4. namespace DelegateAppl
  5. {
  6. class TestDelegate
  7. {
  8. static int num = 10;
  9. public static int AddNum(int p)
  10. {
  11. num += p;
  12. return num;
  13. }
  14.  
  15. public static int MultNum(int q)
  16. {
  17. num *= q;
  18. return num;
  19. }
  20.  
  21. public static int getNum()
  22. {
  23. return num;
  24. }
  25.  
  26. static void Main(string[] args)
  27. {
  28. // 创建委托实例
  29. NumberChanger nc;
  30. NumberChanger nc1 = new NumberChanger(AddNum);
  31. NumberChanger nc2 = new NumberChanger(MultNum);
  32. nc = nc1;
  33. nc += nc2;
  34.  
  35. // 调用多播
  36. nc(5);
  37. Console.WriteLine("Value of Num: {0}", getNum());
  38. Console.ReadKey();
  39. }
  40. }
  41. }

编译执行上述代码,得到如下结果:

  1. Value of Num: 75

委托的使用

下面的示例演示了委托的作用,示例中的 printString 委托可用于引用带有一个字符串作为输入的方法,且不返回数据。

我们使用这个委托来调用两个方法,第一个方法将字符串输出到控制台,第二个方法将字符串输出到文件:

  1. using System;
  2. using System.IO;
  3.  
  4. namespace DelegateAppl
  5. {
  6. class PrintString
  7. {
  8. static FileStream fs;
  9. static StreamWriter sw;
  10.  
  11. // 委托声明
  12. public delegate void printString(string s);
  13.  
  14. // 该方法打印到控制台
  15. public static void WriteToScreen(string str)
  16. {
  17. Console.WriteLine("The String is: {0}", str);
  18. }
  19.  
  20. // 该方法打印到文件
  21. public static void WriteToFile(string s)
  22. {
  23. fs = new FileStream("c:\\message.txt",
  24. FileMode.Append, FileAccess.Write);
  25. sw = new StreamWriter(fs);
  26. sw.WriteLine(s);
  27. sw.Flush();
  28. sw.Close();
  29. fs.Close();
  30. }
  31.  
  32. // 该方法把委托作为参数,并使用它调用方法
  33. // call the methods as required
  34. public static void sendString(printString ps)
  35. {
  36. ps("Hello World");
  37. }
  38. static void Main(string[] args)
  39. {
  40. printString ps1 = new printString(WriteToScreen);
  41. printString ps2 = new printString(WriteToFile);
  42. sendString(ps1);
  43. sendString(ps2);
  44. Console.ReadKey();
  45. }
  46. }
  47. }

编译执行上述代码,得到如下结果:

  1. The String is: Hello World

事件

事件指一个用户操作,如按键、点击、移动鼠标等,也可以是系统生成的通知。当事件发生时,应用需要对其作出相应的反应,如中断。另外,事件也用于内部进程通信。

通过事件使用委托

事件生成于类的声明中,通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件,称为发布器类。其他接受该事件的类称为订阅器类。事件使用的是发布-订阅(publisher-subscriber)模型。

发布器是一个定义了事件和委托的对象,此外还定义了事件和委托之间的联系。一个发布器类的对象调用这个事件,同时通知其他的对象。

订阅器是一个接受事件并提供事件处理程序的对象。在发布器类中的委托调用订阅器类中的方法(或事件处理程序)。

声明事件

在类中声明一个事件,首先需要声明该事件对应的委托类型。如:

  1. public delegate void BoilerLogHandler(string status);

其次为使用 event 关键字来声明这个事件:

  1. //基于上述委托定义事件
  2. public event BoilerLogHandler BoilerEventLog;

上述代码段定义了一个名为 BoilerLogHandler 的委托以及一个名为 BoilerEventLog 的事件,该事件生成时会自动调用委托。

示例 1

  1. using System;
  2. namespace SimpleEvent
  3. {
  4. using System;
  5.  
  6. public class EventTest
  7. {
  8. private int value;
  9. public delegate void NumManipulationHandler();
  10. public event NumManipulationHandler ChangeNum;
  11. protected virtual void OnNumChanged()
  12. {
  13. if (ChangeNum != null)
  14. {
  15. ChangeNum();
  16. }
  17. else
  18. {
  19. Console.WriteLine("Event fired!");
  20. }
  21. }
  22.  
  23. public EventTest(int n )
  24. {
  25. SetValue(n);
  26. }
  27.  
  28. public void SetValue(int n)
  29. {
  30. if (value != n)
  31. {
  32. value = n;
  33. OnNumChanged();
  34. }
  35. }
  36. }
  37.  
  38. public class MainClass
  39. {
  40. public static void Main()
  41. {
  42. EventTest e = new EventTest(5);
  43. e.SetValue(7);
  44. e.SetValue(11);
  45. Console.ReadKey();
  46. }
  47. }
  48. }

编译执行上述代码,得到如下结果:

  1. Event Fired!
  2. Event Fired!
  3. Event Fired!

示例 2

该示例为一个简单的应用程序,该程序用于热水锅炉系统故障排除。当维修工程师检查锅炉时,锅炉的温度、压力以及工程师所写的备注都会被自动记录到一个日志文件中。

  1. using System;
  2. using System.IO;
  3.  
  4. namespace BoilerEventAppl
  5. {
  6. // boiler 类
  7. class Boiler
  8. {
  9. private int temp;
  10. private int pressure;
  11. public Boiler(int t, int p)
  12. {
  13. temp = t;
  14. pressure = p;
  15. }
  16.  
  17. public int getTemp()
  18. {
  19. return temp;
  20. }
  21.  
  22. public int getPressure()
  23. {
  24. return pressure;
  25. }
  26. }
  27.  
  28. // 事件发布器
  29. class DelegateBoilerEvent
  30. {
  31. public delegate void BoilerLogHandler(string status);
  32.  
  33. // 基于上述委托定义事件
  34. public event BoilerLogHandler BoilerEventLog;
  35.  
  36. public void LogProcess()
  37. {
  38. string remarks = "O. K";
  39. Boiler b = new Boiler(100, 12);
  40. int t = b.getTemp();
  41. int p = b.getPressure();
  42. if(t > 150 || t < 80 || p < 12 || p > 15)
  43. {
  44. remarks = "Need Maintenance";
  45. }
  46. OnBoilerEventLog("Logging Info:\n");
  47. OnBoilerEventLog("Temparature " + t + "\nPressure: " + p);
  48. OnBoilerEventLog("\nMessage: " + remarks);
  49. }
  50.  
  51. protected void OnBoilerEventLog(string message)
  52. {
  53. if (BoilerEventLog != null)
  54. {
  55. BoilerEventLog(message);
  56. }
  57. }
  58. }
  59.  
  60. // 该类保留写入日志文件的条款
  61. class BoilerInfoLogger
  62. {
  63. FileStream fs;
  64. StreamWriter sw;
  65. public BoilerInfoLogger(string filename)
  66. {
  67. fs = new FileStream(filename, FileMode.Append, FileAccess.Write);
  68. sw = new StreamWriter(fs);
  69. }
  70.  
  71. public void Logger(string info)
  72. {
  73. sw.WriteLine(info);
  74. }
  75.  
  76. public void Close()
  77. {
  78. sw.Close();
  79. fs.Close();
  80. }
  81. }
  82.  
  83. // 事件订阅器
  84. public class RecordBoilerInfo
  85. {
  86. static void Logger(string info)
  87. {
  88. Console.WriteLine(info);
  89. }//end of Logger
  90.  
  91. static void Main(string[] args)
  92. {
  93. BoilerInfoLogger filelog = new BoilerInfoLogger("e:\\boiler.txt");
  94. DelegateBoilerEvent boilerEvent = new DelegateBoilerEvent();
  95. boilerEvent.BoilerEventLog += new
  96. DelegateBoilerEvent.BoilerLogHandler(Logger);
  97. boilerEvent.BoilerEventLog += new
  98. DelegateBoilerEvent.BoilerLogHandler(filelog.Logger);
  99. boilerEvent.LogProcess();
  100. Console.ReadLine();
  101. filelog.Close();
  102. }//end of main
  103.  
  104. }//end of RecordBoilerInfo
  105. }

编译执行上述代码,得到如下结果:

  1. Logging info:
  2.  
  3. Temperature 100
  4. Pressure 12
  5.  
  6. Message: O. K

集合

集合类专门用于数据存储和数据检索,并提供堆栈、队列、列表和哈希表的支持。目前,大多数集合类都实现了相同的接口。

集合类服务于不同的目的,如为元素动态分配内存,基于索引访问列表项等等,这些类所创建的是 Object 类的对象的集合。在 C# 中,Object 类是所有数据类型的基类。

各种集合类及其用法

下表为一些常用的以 System.Collection 为命名空间的集合类,点击相应链接,可查看详细说明。

描述及用法
动态数组 动态数组表示可被单独索引的对象的有序集合。 动态数组基本上可以替代数组,但与数组不同的是,通过索引,动态数组可以在指定的位置添加和移除项目,且会自动重新调整大小,同样允许在列表中进行动态内存分配、增加、搜索、排序各项。
哈希表 哈希表使用来访问集合中的元素。 当需要通过键访问元素时,则使用哈希表,且一个有用的键值可以很方便地被识别。哈希表中的每一项都有一个键/值对。键用于访问集合中的项目。
排序列表 排序列表使用索引来访问列表中的项。 它是数组和哈希表的组合,包含一个可使用键或索引访问各项的列表。若使用索引来访问各项,则它为一个动态数组,若使用键来访问各项,则它为一个哈希表。集合中的各项总是按键值排序。
堆栈 堆栈表示的是一个后进先出的对象集合。 当需要对各项进行后进先出的访问时,则使用堆栈。在列表中添加一项,称为推入元素;从列表中移除一项时,称为弹出元素。
队列 队列表示的是一个先进先出的对象集合。 当需要对各项进行先进先出的访问时,则使用队列。在列表中添加一项,称为入队;从列表中移除一项,称为出队
点阵列 点阵列表示的是一个使用值 1 和 0 来表示的二进制数组。 当需要存储位,但事先不知道位数时,则使用点阵列。通过整型索引,可以从点阵列集合中访问各项,该索引值从零开始。

泛型

泛型允许推迟类或方法中编程元素的数据类型规范的编写,直到实际在程序中使用它的时候再编写。换句话说,泛型允许编写一个可以与任何数据类型协作的类或方法。

你可以通过数据类型的替代参数来编写类或方法的规范。当编译器遇到类的构造函数或方法的函数调用时,它会生成代码来处理指定的数据类型。下面这个简单的示例将有助于理解这个概念:

  1. using System;
  2. using System.Collections.Generic;
  3.  
  4. namespace GenericApplication
  5. {
  6. public class MyGenericArray<T>
  7. {
  8. private T[] array;
  9. public MyGenericArray(int size)
  10. {
  11. array = new T[size + 1];
  12. }
  13.  
  14. public T getItem(int index)
  15. {
  16. return array[index];
  17. }
  18.  
  19. public void setItem(int index, T value)
  20. {
  21. array[index] = value;
  22. }
  23. }
  24.  
  25. class Tester
  26. {
  27. static void Main(string[] args)
  28. {
  29.  
  30. // 声明一个整型数组
  31. MyGenericArray<int> intArray = new MyGenericArray<int>(5);
  32.  
  33. // 设置值
  34. for (int c = 0; c < 5; c++)
  35. {
  36. intArray.setItem(c, c*5);
  37. }
  38.  
  39. // 获取值
  40. for (int c = 0; c < 5; c++)
  41. {
  42. Console.Write(intArray.getItem(c) + " ");
  43. }
  44.  
  45. Console.WriteLine();
  46.  
  47. // 声明一个字符数组
  48. MyGenericArray<char> charArray = new MyGenericArray<char>(5);
  49.  
  50. // 设置值
  51. for (int c = 0; c < 5; c++)
  52. {
  53. charArray.setItem(c, (char)(c+97));
  54. }
  55.  
  56. // 获取值
  57. for (int c = 0; c< 5; c++)
  58. {
  59. Console.Write(charArray.getItem(c) + " ");
  60. }
  61. Console.WriteLine();
  62.  
  63. Console.ReadKey();
  64. }
  65. }
  66. }

编译执行上述代码,得到如下结果:

  1. 0 5 10 15 20
  2. a b c d e

泛型的特性

泛型是一种可以增强程序功能的技术,表现在如下几个方面:

  • 它有助于最大限度地进行重用代码、确保类型的安全以及提高性能。
  • 你可以创建泛型集合类。.NET 框架类库在 System.Collections.Generic 命名空间中包含了一些新的泛型集合类,你可以使用这些泛型集合类来替代 System.Collections 中的集合类。
  • 你可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
  • 你可以对泛型类进行约束,使其只访问具有特定数据类型的方法。
  • 在运行时,通过使用反射方法可以获取泛型数据类型中所使用的类型信息。

泛型方法

在之前的例子中,我们使用过一个泛型类,我们还可以通过类型参数来声明泛型方法。下述示例很好地展示了这个概念:

  1. using System;
  2. using System.Collections.Generic;
  3.  
  4. namespace GenericMethodAppl
  5. {
  6. class Program
  7. {
  8. static void Swap<T>(ref T lhs, ref T rhs)
  9. {
  10. T temp;
  11. temp = lhs;
  12. lhs = rhs;
  13. rhs = temp;
  14. }
  15. static void Main(string[] args)
  16. {
  17. int a, b;
  18. char c, d;
  19. a = 10;
  20. b = 20;
  21. c = 'I';
  22. d = 'V';
  23.  
  24. // 显示交换之前的值
  25. Console.WriteLine("Int values before calling swap:");
  26. Console.WriteLine("a = {0}, b = {1}", a, b);
  27. Console.WriteLine("Char values before calling swap:");
  28. Console.WriteLine("c = {0}, d = {1}", c, d);
  29.  
  30. // 调用 swap 进行交换
  31. Swap<int>(ref a, ref b);
  32. Swap<char>(ref c, ref d);
  33.  
  34. // 显示交换之后的值
  35. Console.WriteLine("Int values after calling swap:");
  36. Console.WriteLine("a = {0}, b = {1}", a, b);
  37. Console.WriteLine("Char values after calling swap:");
  38. Console.WriteLine("c = {0}, d = {1}", c, d);
  39.  
  40. Console.ReadKey();
  41. }
  42. }
  43. }

编译执行上述代码,得到如下结果:

  1. Int values before calling swap:
  2. a = 10, b = 20
  3. Char values before calling swap:
  4. c = I, d = V
  5. Int values after calling swap:
  6. a = 20, b = 10
  7. Char values after calling swap:
  8. c = V, d = I

泛型委托

你可以通过类型参数来定义一个泛型委托,如:

  1. delegate T NumberChanger<T>(T n);

泛型委托示例:

  1. using System;
  2. using System.Collections.Generic;
  3.  
  4. delegate T NumberChanger<T>(T n);
  5. namespace GenericDelegateAppl
  6. {
  7. class TestDelegate
  8. {
  9. static int num = 10;
  10. public static int AddNum(int p)
  11. {
  12. num += p;
  13. return num;
  14. }
  15.  
  16. public static int MultNum(int q)
  17. {
  18. num *= q;
  19. return num;
  20. }
  21. public static int getNum()
  22. {
  23. return num;
  24. }
  25.  
  26. static void Main(string[] args)
  27. {
  28. // 创建委托实例
  29. NumberChanger<int> nc1 = new NumberChanger<int>(AddNum);
  30. NumberChanger<int> nc2 = new NumberChanger<int>(MultNum);
  31.  
  32. // 使用委托对象调用方法
  33. nc1(25);
  34. Console.WriteLine("Value of Num: {0}", getNum());
  35. nc2(5);
  36. Console.WriteLine("Value of Num: {0}", getNum());
  37. Console.ReadKey();
  38. }
  39. }
  40. }

编译执行以上代码,得到如下结果:

  1. Value of Num: 35
  2. Value of Num: 175

匿名方法

先前的章节中提过,委托是用于引用与其具有相同签名的方法,即使用委托对象,就可以调用任何被该委托引用的方法。

匿名方法提供了一种将一段代码块作为委托参数的技术。顾名思义,匿名方法没有名字,只有方法主体。

你不需要为匿名方法指定返回类型,其返回类型直接由方法主体推断而来。

编写匿名方法

匿名方法通过使用 delegate 关键字创建委托实例来实现方法的声明,如:

  1. delegate void NumberChanger(int n);
  2. ...
  3. NumberChanger nc = delegate(int x)
  4. {
  5. Console.WriteLine("Anonymous Method: {0}", x);
  6. };

上述代码块中的 Console.WriteLine("Anonymous Method: {0}", x); 就是匿名方法的主体。

委托可以通过匿名方法调用,也可以通过命名方法调用,即,通过向委托对象来传递方法参数,如:

  1. nc(10);

示例

  1. using System;
  2.  
  3. delegate void NumberChanger(int n);
  4. namespace DelegateAppl
  5. {
  6. class TestDelegate
  7. {
  8. static int num = 10;
  9. public static void AddNum(int p)
  10. {
  11. num += p;
  12. Console.WriteLine("Named Method: {0}", num);
  13. }
  14.  
  15. public static void MultNum(int q)
  16. {
  17. num *= q;
  18. Console.WriteLine("Named Method: {0}", num);
  19. }
  20.  
  21. public static int getNum()
  22. {
  23. return num;
  24. }
  25. static void Main(string[] args)
  26. {
  27. //使用匿名方法创建委托实例
  28. NumberChanger nc = delegate(int x)
  29. {
  30. Console.WriteLine("Anonymous Method: {0}", x);
  31. };
  32.  
  33. //使用匿名方法调用委托
  34. nc(10);
  35.  
  36. //使用命名方法实例化委托
  37. nc = new NumberChanger(AddNum);
  38.  
  39. //使用命名方法调用委托
  40. nc(5);
  41.  
  42. //使用另一个命名方法实例化委托
  43. nc = new NumberChanger(MultNum);
  44.  
  45. //使用另一个命名方法调用委托
  46. nc(2);
  47. Console.ReadKey();
  48. }
  49. }
  50. }

编译执行上述代码,得到如下结果:

  1. Anonymous Method: 10
  2. Named Method: 15
  3. Named Method: 30

不安全代码

当代码段被 unsafe 修饰符标记时,C# 允许该代码段中的函数使用指针变量,故使用了指针变量的代码块又被称为不安全代码或非托管代码。

注意: 若要在 codingground 中执行本章的程序,请将 Project >> Compile Options >> Compilation Command to 中的编辑项设置为 mcs *.cs -out:main.exe -unsafe"

指针

指针是指其值为另一个变量的地址的变量,即内存位置的直接地址。如一般的变量或常量一样,在使用指针来存储其他变量的地址之前,必须先进行指针声明。

声明指针变量的一般形式为:

  1. type *var-name;

以下为一些有效的指针声明示例:

  1. int *ip; /* pointer to an integer */
  2. double *dp; /* pointer to a double */
  3. float *fp; /* pointer to a float */
  4. char *ch /* pointer to a character */

下述示例为在 C# 中使用了 unsafe 修饰符时指针的使用:

  1. using System;
  2. namespace UnsafeCodeApplication
  3. {
  4. class Program
  5. {
  6. static unsafe void Main(string[] args)
  7. {
  8. int var = 20;
  9. int* p = &var;
  10. Console.WriteLine("Data is: {0} ", var);
  11. Console.WriteLine("Address is: {0}", (int)p);
  12. Console.ReadKey();
  13. }
  14. }
  15. }

编译执行上述代码,得到如下结果:

  1. Data is: 20
  2. Address is: 99215364

除了将整个方法声明为不安全代码之外,还可以只将部分代码声明为不安全代码,下面章节中的的示例说明了这点。

使用指针检索数据值

使用 ToString() 方法可以检索存储在指针变量所引用位置的数据。例如:

  1. using System;
  2. namespace UnsafeCodeApplication
  3. {
  4. class Program
  5. {
  6. public static void Main()
  7. {
  8. unsafe
  9. {
  10. int var = 20;
  11. int* p = &var;
  12. Console.WriteLine("Data is: {0} " , var);
  13. Console.WriteLine("Data is: {0} " , p->ToString());
  14. Console.WriteLine("Address is: {0} " , (int)p);
  15. }
  16.  
  17. Console.ReadKey();
  18. }
  19. }
  20. }

编译执行上述代码,得到如下结果

  1. Data is: 20
  2. Data is: 20
  3. Address is: 77128984

传递指针作为方法的参数

指针可以作为方法中的参数,例如:

  1. using System;
  2. namespace UnsafeCodeApplication
  3. {
  4. class TestPointer
  5. {
  6. public unsafe void swap(int* p, int *q)
  7. {
  8. int temp = *p;
  9. *p = *q;
  10. *q = temp;
  11. }
  12.  
  13. public unsafe static void Main()
  14. {
  15. TestPointer p = new TestPointer();
  16. int var1 = 10;
  17. int var2 = 20;
  18. int* x = &var1;
  19. int* y = &var2;
  20.  
  21. Console.WriteLine("Before Swap: var1:{0}, var2: {1}", var1, var2);
  22. p.swap(x, y);
  23.  
  24. Console.WriteLine("After Swap: var1:{0}, var2: {1}", var1, var2);
  25. Console.ReadKey();
  26. }
  27. }
  28. }

编译执行上述代码,得到如下结果:

  1. Before Swap: var1: 10, var2: 20
  2. After Swap: var1: 20, var2: 10

使用指针访问数组元素

在 C# 中,数组名称与一个指向与数组数据具有相同数据类型的指针是不同的变量类型。例如 int *p 和 int[] 是不同的类型。你可以对指针变量 p 进行加操作,因为它在内存中是不固定的,反之,数组的地址在内存中是固定的,因而无法对其直接进行加操作。

故如同 C 或 C++,这里同样需要使用一个指针变量来访问数组数据,并且需要使用 fixed 关键字来固定指针,示例如下:

  1. using System;
  2. namespace UnsafeCodeApplication
  3. {
  4. class TestPointer
  5. {
  6. public unsafe static void Main()
  7. {
  8. int[] list = {10, 100, 200};
  9. fixed(int *ptr = list)
  10.  
  11. /* let us have array address in pointer */
  12. for ( int i = 0; i < 3; i++)
  13. {
  14. Console.WriteLine("Address of list[{0}]={1}",i,(int)(ptr + i));
  15. Console.WriteLine("Value of list[{0}]={1}", i, *(ptr + i));
  16. }
  17.  
  18. Console.ReadKey();
  19. }
  20. }
  21. }

编译执行上述代码,得到如下结果:

  1. Address of list[0] = 31627168
  2. Value of list[0] = 10
  3. Address of list[1] = 31627172
  4. Value of list[1] = 100
  5. Address of list[2] = 31627176
  6. Value of list[2] = 200

编译不安全代码

必须切换命令行编译器到指定的 /unsafe 命令行才能进行不安全代码的编译。

例如,编译一个包含不安全代码的名为 prog1.cs 的程序,需要在命令行中输入如下命令:

  1. csc /unsafe prog1.cs

在 Visual Studio IDE 环境中编译不安全代码,需要在项目属性中启用相关设置。

步骤如下:

  • 双击资源管理器中的属性节点,打开项目属性。
  • 点击 Build 标签页。
  • 选择选项 "Allow unsafe code"。

多线程

线程的定义是程序的执行路径。每个线程都定义了一个独特的控制流,如果应用程序涉及到复杂且耗时的操作,那么设置不同的线程执行路径会非常有好处,因为每个线程会被指定于执行特定的工作。

线程实际上是轻量级进程。一个常见的使用线程的实例是现代操作系统中的并行编程。使用线程不仅有效地减少了 CPU 周期的浪费,同时还提高了应用程序的运行效率。

到目前为止我们所编写的程序都是以一个单线程作为应用程序的运行的,其运行过程均为单一的。但是,在这种情况下,应用程序在同一时间只能执行一个任务。为了使应用程序可以同时执行多个任务,需要将其被划分为多个更小的线程。

线程的声明周期

线程的生命周期开始于对象的 System.Threading.Thread 类创建时,结束于线程被终止或是完成执行时。 下列各项为线程在生命周期中的各种状态:

  • 未启动状态:线程实例已被创建但 Start 方法仍未被调用时的状态。
  • 就绪状态:线程已准备好运行,正在等待 CPU 周期时的状态。
  • 不可运行状态:下面的几种情况下线程是不可运行的:
    • 已经调用 Sleep 方法
    • 已经调用 Wait 方法
    • 通过 I/O 操作阻塞
  • 死亡状态:线程已完成执行或已终止的状态。

主线程

在 C# 中,System.Threading.Thread 类用于线程的工作。它允许创建并访问多线程应用程序中的单个线程。进程中第一个被执行的线程称为主线程。

当 C# 程序开始执行时,会自动创建主线程。使用 Thread 类创建的线程被主线程的子线程调用。通过 Thread 类的 CurrentThread 属性可以访问线程。

下面的程序演示了主线程的执行:

  1. using System;
  2. using System.Threading;
  3.  
  4. namespace MultithreadingApplication
  5. {
  6. class MainThreadProgram
  7. {
  8. static void Main(string[] args)
  9. {
  10. Thread th = Thread.CurrentThread;
  11. th.Name = "MainThread";
  12. Console.WriteLine("This is {0}", th.Name);
  13. Console.ReadKey();
  14. }
  15. }
  16. }

编译执行上述代码,得到如下结果:

  1. This is MainThread

Thread 类的属性和方法

下表为 Thread 类一些常用的属性:

属性 描述
CurrentContext 获取线程当前正在执行的线程的上下文。
CurrentCulture 获取或设置当前线程的区域性
CurrentPrinciple 获取或设置线程的当前责任人(针对基于角色的安全性)
CurrentThread 获取当前正在运行的线程
CurrentUICulture 获取或设置资源管理器当前所使用的区域性,便于在运行时查找区域性特定的资源
ExecutionContext 获取一个 ExcutionContext 对象,该对象包含有关当前线程的各种上下文信息。
IsAlive 获取一个值,指示当前线程的执行状态。
IsBackground 获取或设置一个值,指示线程是否为后台线程。
IsThreadPoolThread 获取或设置一个值,指示线程是否属于托管线程池。
ManagedThreadId 获取当前托管线程的唯一标识符
Name 获取或设置线程的名称。
Priority 获取或设置一个值,指示线程的调度优先级
ThreadState 获取一个值,指示当前线程的状态。

下表为 Thread 类一些常用的方法:

序号 方法名和描述
1 public void Abort() 在调用此方法的线程上引发 ThreadAbortException,则触发终止此线程的操作。调用此方法通常会终止线程。
2 public static LocalDataStoreSlot AllocateDataSlot() 在所有的线程上分配未命名的数据槽。使用以 ThreadStaticAttribute 属性标记的字段,可获得更好的性能。
3 public static LocalDataStoreSlot AllocateNamedDataSlot( string name) 在所有线程上分配已命名的数据槽。使用以 ThreadStaticAttribute 属性标记的字段,可获得更好的性能。
4 public static void BeginCriticalRegion() 通知主机将要进入一个代码区域,若该代码区域内的线程终止或发生未经处理的异常,可能会危害应用程序域中的其他任务。
5 public static void BeginThreadAffinity() 通知主机托管代码将要执行依赖于当前物理操作系统线程的标识的指令。
6 public static void EndCriticalRegion() 通知主机将要进入一个代码区域,若该代码区域内的线程终止或发送未经处理的异常,仅会影响当前任务。
7 public static void EndThreadAffinity() 通知主机托管代码已执行完依赖于当前物理操作系统线程的标识的指令。
8 public static void FreeNamedDataSlot(string name) 消除进程中所有线程的名称与槽之间的关联。使用以 ThreadStaticAttribute 属性标记的字段,可获得更好的性能。
9 public static Object GetData( LocalDataStoreSlot slot ) 在当前线程的当前域中从当前线程上指定的槽中检索值。使用以 ThreadStaticAttribute 属性标记的字段,可获得更好的性能。
10 public static AppDomain GetDomain() 返回当前线程正在其中运行的当前域。
11 public static AppDomain GetDomainID() 返回唯一的应用程序域标识符。
12 public static LocalDataStoreSlot GetNamedDataSlot( string name ) 查找已命名的数据槽。使用以 ThreadStaticAttribute 属性标记的字段,可获得更好的性能。
13 public void Interrupt() 中断处于 WaitSleepJoin 线程状态的线程。
14 public void Join() 在继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻塞调用线程,直到某个线程终止为止。此方法有不同的重载形式。
15 public static void MemoryBarrier() 按如下方式同步内存存取:执行当前线程的处理器在对指令进行重新排序时,不能采用先执行 MemoryBarrier 调用之后的内存存取,再执行 MemoryBarrier 调用之前的内存存取的方式。
16 public static void ResetAbort() 取消当前线程请求的 Abort 操作。
17 public static void SetData( LocalDataStoreSlot slot, Object data ) 在指定槽中,设置当前正在运行中线程的当前域的数据。使用以 ThreadStaticAttribute 属性标记的字段,可获得更好的性能。
18 public void Start() 开始一个线程。
19 public static void Sleep( int millisecondsTimeout ) 令线程暂停一段时间。
20 public static void SpinWait( int iterations ) 令线程等待一段时间,时间长度由 iterations 参数定义。
21 public static byte VolatileRead( ref byte address );public static double VolatileRead( ref double address );public static int VolatileRead( ref int address );public static Object VolatileRead( ref Object address ) 读取字段的值。无论处理器的数目或处理器缓存的状态如何,该值都表示由计算机中任何一个处理器写入的最新值。此方法有不同的重载形式,此处仅给出部分例子。
22 public static void VolatileWrite( ref byte address, byte value );public static void VolatileWrite( ref double address, double value );public static void VolatileWrite( ref int address, int value );public static void VolatileWrite( ref Object address, Object value ) 立即写入一个值到字段中,使该值对计算机中的所有处理器都可见。此方法有不同的重载形式,此处仅给出部分例子。
23 public static bool Yield() 令调用线程执行已准备好在当前处理器上运行的另一个线程,由操作系统选择要执行的线程。

线程的创建

线程是通过扩展 Thread 类创建的,扩展而来的 Thread 类调用 Start() 方法即可开始子线程的执行。 示例:

  1. using System;
  2. using System.Threading;
  3.  
  4. namespace MultithreadingApplication
  5. {
  6. class ThreadCreationProgram
  7. {
  8. public static void CallToChildThread()
  9. {
  10. Console.WriteLine("Child thread starts");
  11. }
  12.  
  13. static void Main(string[] args)
  14. {
  15. ThreadStart childref = new ThreadStart(CallToChildThread);
  16. Console.WriteLine("In Main: Creating the Child thread");
  17. Thread childThread = new Thread(childref);
  18. childThread.Start();
  19. Console.ReadKey();
  20. }
  21. }
  22. }

编译执行上述代码段,得到如下结果:

  1. In Main: Creating the Child thread
  2. Child thread starts

线程的管理

Thread 类提供了多种用于线程管理的方法。 下面的示例调用了 sleep() 方法来在一段特定时间内暂停线程:

  1. using System;
  2. using System.Threading;
  3.  
  4. namespace MultithreadingApplication
  5. {
  6. class ThreadCreationProgram
  7. {
  8. public static void CallToChildThread()
  9. {
  10. Console.WriteLine("Child thread starts");
  11.  
  12. // 令线程暂停 5000 毫秒
  13. int sleepfor = 5000;
  14.  
  15. Console.WriteLine("Child Thread Paused for {0} seconds", sleepfor / 1000);
  16. Thread.Sleep(sleepfor);
  17. Console.WriteLine("Child thread resumes");
  18. }
  19.  
  20. static void Main(string[] args)
  21. {
  22. ThreadStart childref = new ThreadStart(CallToChildThread);
  23. Console.WriteLine("In Main: Creating the Child thread");
  24. Thread childThread = new Thread(childref);
  25. childThread.Start();
  26. Console.ReadKey();
  27. }
  28. }
  29. }

编译执行上述代码,得到如下代码段:

  1. In Main: Creating the Child thread
  2. Child thread starts
  3. Child Thread Paused for 5 seconds
  4. Child thread resumes

线程的销毁

使用 Abort() 方法可销毁线程。

在运行时,通过抛出 ThreadAbortException 来终止线程。这个异常无法被捕获,当且仅当具备 finally 块时,才将控制送到 finally 块中。

示例:

  1. using System;
  2. using System.Threading;
  3.  
  4. namespace MultithreadingApplication
  5. {
  6. class ThreadCreationProgram
  7. {
  8. public static void CallToChildThread()
  9. {
  10. try
  11. {
  12. Console.WriteLine("Child thread starts");
  13.  
  14. // 执行一些任务,如计十个数
  15. for (int counter = 0; counter <= 10; counter++)
  16. {
  17. Thread.Sleep(500);
  18. Console.WriteLine(counter);
  19. }
  20.  
  21. Console.WriteLine("Child Thread Completed");
  22. }
  23.  
  24. catch (ThreadAbortException e)
  25. {
  26. Console.WriteLine("Thread Abort Exception");
  27. }
  28. finally
  29. {
  30. Console.WriteLine("Couldn't catch the Thread Exception");
  31. }
  32. }
  33.  
  34. static void Main(string[] args)
  35. {
  36. ThreadStart childref = new ThreadStart(CallToChildThread);
  37. Console.WriteLine("In Main: Creating the Child thread");
  38. Thread childThread = new Thread(childref);
  39. childThread.Start();
  40.  
  41. // 将主线程停止一段时间
  42. Thread.Sleep(2000);
  43.  
  44. // 中止子线程
  45. Console.WriteLine("In Main: Aborting the Child thread");
  46.  
  47. childThread.Abort();
  48. Console.ReadKey();
  49. }
  50. }
  51. }

编译执行上述代码,得到如下结果:

  1. In Main: Creating the Child thread
  2. Child thread starts
  3. 0
  4. 1
  5. 2
  6. In Main: Aborting the Child thread
  7. Thread Abort Exception
  8. Couldn't catch the Thread Exception