导图社区 C高级教程
包含C# 特性(Attribute)、C# 委托(Delegate)、事件/泛型等基本概念及使用。
编辑于2020-10-25 11:48:20C# 高级教程
C# 特性(Attribute)
特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。
特性使用(Attribute)
可以通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所应用的元素前面的方括号([ ])来描述的。 规定特性(Attribute)的语法如下: [attribute(positional_parameters, name_parameter = value, ...)] element
预定义特性(Attribute)
.Net 框架提供了三种预定义特性:
AttributeUsage
AttributeUsage 描述了如何使用一个自定义特性类。它规定了特性可应用到的项目的类型。 规定该特性的语法如下: [AttributeUsage( validon, AllowMultiple=allowmultiple, Inherited=inherited )] 其中: 参数 validon 规定特性可被放置的语言元素。它是枚举器 AttributeTargets 的值的组合。默认值是 AttributeTargets.All。 参数 allowmultiple(可选的)为该特性的 AllowMultiple 属性(property)提供一个布尔值。如果为 true,则该特性是多用的。默认值是 false(单用的)。 参数 inherited(可选的)为该特性的 Inherited 属性(property)提供一个布尔值。如果为 true,则该特性可被派生类继承。默认值是 false(不被继承)。 例如: [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)]
Conditional
预定义特性标记了一个条件方法,其执行依赖于指定的预处理标识符。 它会引起方法调用的条件编译,取决于指定的值,比如 Debug 或 Trace。例如,当调试代码时显示变量的值。 规定该特性的语法如下: [Conditional( conditionalSymbol )] 例如: [Conditional("DEBUG")]
Obsolete
预定义特性标记了不应被使用的程序实体。它可以让您通知编译器丢弃某个特定的目标元素。例如,当一个新方法被用在一个类中,但是您仍然想要保持类中的旧方法,您可以通过显示一个应该使用新方法,而不是旧方法的消息,来把它标记为 obsolete(过时的)。 规定该特性的语法如下: [Obsolete( message )] [Obsolete( message, iserror )] 其中: 参数 message,是一个字符串,描述项目为什么过时以及该替代使用什么。 参数 iserror,是一个布尔值。如果该值为 true,编译器应把该项目的使用当作一个错误。默认值是 false(编译器生成一个警告)。 下面的实例演示了该特性: 实例 using System; public class MyClass { [Obsolete("Don't use OldMethod, use NewMethod instead", true)] static void OldMethod() { Console.WriteLine("It is the old method"); } static void NewMethod() { Console.WriteLine("It is the new method"); } public static void Main() { OldMethod(); } } 当您尝试编译该程序时,编译器会给出一个错误消息说明: Don't use OldMethod, use NewMethod instead
自定义特性
.Net 框架允许创建自定义特性,用于存储声明性的信息,且可在运行时被检索。该信息根据设计标准和应用程序需要,可与任何目标元素相关。
创建并使用自定义特性包含四个步骤
声明自定义特性
一个新的自定义特性应派生自 System.Attribute 类。例如: // 一个自定义特性 BugFix 被赋给类及其成员 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)] public class DeBugInfo : System.Attribute{ // }
构建自定义特性
以构建一个名为 DeBugInfo 的自定义特性为例 : 该特性将存储调试程序获得的信息。它存储下面的信息: bug 的代码编号 辨认该 bug 的开发人员名字 最后一次审查该代码的日期 一个存储了开发人员标记的字符串消息 下面的代码演示了 DeBugInfo 类: 实例 // 一个自定义特性 BugFix 被赋给类及其成员 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)] public class DeBugInfo : System.Attribute { //这个DeBugInfo 类将带有三个用于存储前三个信息的私有属性(property)和一个用于存储消息的公有属性(property)。 //所以 bug 编号、开发人员名字和审查日期将是 DeBugInfo 类的必需的定位( positional)参数,消息将是一个可选的命名(named)参数。 private int bugNo; private string developer; private string lastReview; public string message; public DeBugInfo(int bg, string dev, string d)//每个特性必须至少有一个构造函数。必需的定位( positional)参数应通过构造函数传递。 { this.bugNo = bg; this.developer = dev; this.lastReview = d; } public int BugNo { get { return bugNo; } } public string Developer { get { return developer; } } public string LastReview { get { return lastReview; } } public string Message { get { return message; } set { message = value; } } }
在目标程序元素上应用自定义特性
通过把特性放置在紧接着它的目标之前,来应用该特性: 实例 [DeBugInfo(45, "Zara Ali", "12/8/2012", Message = "Return type mismatch")] [DeBugInfo(49, "Nuha Ali", "10/10/2012", Message = "Unused variable")] class Rectangle { // 成员变量 protected double length; protected double width; public Rectangle(double l, double w) { length = l; width = w; } [DeBugInfo(55, "Zara Ali", "19/10/2012", Message = "Return type mismatch")] public double GetArea() { return length * width; } [DeBugInfo(56, "Zara Ali", "19/10/2012")] public void Display() { Console.WriteLine("Length: {0}", length); Console.WriteLine("Width: {0}", width); Console.WriteLine("Area: {0}", GetArea()); } }
通过反射访问特性
using System; using System.Reflection;//System.Reflection 类的 MemberInfo用于发现与类相关的特性(attribute)。 namespace BugFixApplication { // 一个自定义特性 BugFix 被赋给类及其成员 [AttributeUsage #region//定义了特性能被放在那些前面 (AttributeTargets.Class |//规定了特性能被放在class的前面 AttributeTargets.Constructor |//规定了特性能被放在构造函数的前面 AttributeTargets.Field |//规定了特性能被放在域的前面 AttributeTargets.Method |//规定了特性能被放在方法的前面 AttributeTargets.Property,//规定了特性能被放在属性的前面 #endregion AllowMultiple = true)]//这个属性标记了我们的定制特性能否被重复放置在同一个程序实体前多次。 public class DeBugInfo : System.Attribute//继承了预定义特性后的自定义特性 { private int bugNo; private string developer; private string lastReview; public string message; public DeBugInfo(int bg,string dev,string d)//构造函数,接收三个参数并赋给对应实例 { this.bugNo = bg; this.developer = dev; this.lastReview = d; } #region//定义对应的调用,返回对应值value public int BugNo { get { return bugNo; } } public string Developer { get { return developer; } } public string LastReview { get { return lastReview; } } //前面有public string message; public string Message//定义了可以通过Message = "",来对message进行赋值。 //因为不在构造函数中,所以是可选的 { get {return message;} set {message = value;} } /* * 这部分可以简写如下 * public string Message{get;set;} */ } #endregion [DeBugInfo(45, "Zara Ali", "12/8/2012", Message = "Return type mismatch")] [DeBugInfo(49, "Nuha Ali", "10/10/2012", Message = "Unused variable")]//前面定义时的AllowMultiple=ture允许了多次使用在同一地方 class Rectangle { protected double length; protected double width;//定义两个受保护的(封装)的成员变量 public Rectangle(double l,double w)//构造函数,对两个成员变量进行初始化,公开的 { length = l; width = w; } [DeBugInfo(55, "Zara Ali", "19/10/2012", Message = "Return type mismatch")] public double GetArea() { return length * width; } [DeBugInfo(56, "Zara Ali", "19/10/2012")]//因为message是可选项,所以可以不给出 //不给出即为null,为空白 public void Display() { Console.WriteLine("Length: {0}", length); Console.WriteLine("Width:{0}", width); Console.WriteLine("Area:{0}", GetArea());//常规打印 } } class ExecuteRectangle { static void Main(string[] args)//程序入口 { Rectangle r = new Rectangle(4.5, 7.5);//实例化 r.Display();//执行打印长、宽、面积 Type type = typeof(Rectangle);//让type对应这个Rectangle类 // 遍历 Rectangle 类的特性 foreach (Object attributes in type.GetCustomAttributes(false))//遍历Rectangle的所有特性 { DeBugInfo dbi = (DeBugInfo)attributes;//强制转换 if(null != dbi)//dbi非空 { Console.WriteLine("Bug on: {0}", dbi.BugNo); Console.WriteLine("Developer: {0}", dbi.Developer); Console.WriteLine("Last REviewed: {0}", dbi.LastReview); Console.WriteLine("Remarks: {0}", dbi.Message); } } // 遍历方法特性 foreach (MethodInfo m in type.GetMethods())//遍历Rectangle类下的所有方法 { foreach (Attribute a in m.GetCustomAttributes(true))//遍历每个方法的特性 { DeBugInfo dbi = a as DeBugInfo;//通过 object 声明对象,是用了装箱和取消装箱的概念. //也就是说 object 可以看成是所有类型的父类。 //因此 object 声明的对象可以转换成任意类型的值。 //通过拆装箱代替强制转换 if (null !=dbi)//同理打印 { Console.WriteLine("BugFixApplication no: {0},for Method: {1}", dbi.BugNo, m.Name); Console.WriteLine("Developer:{0}", dbi.Developer); Console.WriteLine("Last Reviewed: {0}", dbi.LastReview); Console.WriteLine("Remarks: {0}", dbi.Message); } } } Console.ReadKey(); } } }
C# 反射(Reflection)
反射指程序可以访问、检测和修改它本身状态或行为的一种能力。 程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。 您可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。
优缺点
优点: 1、反射提高了程序的灵活性和扩展性。 2、降低耦合性,提高自适应能力。 3、它允许程序创建和控制任何类的对象,无需提前硬编码目标类。 缺点: 1、性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。 2、使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。
反射(Reflection)的用途
它允许在运行时查看特性(attribute)信息。 它允许审查集合中的各种类型,以及实例化这些类型。 它允许延迟绑定的方法和属性(property)。 它允许在运行时创建新类型,然后使用这些类型执行一些任务。
查看元数据
System.Reflection 类的 MemberInfo 对象需要被初始化,用于发现与类相关的特性(attribute)。为了做到这点,您可以定义目标类的一个对象,如下: System.Reflection.MemberInfo info = typeof(MyClass); 实例 using System; [AttributeUsage(AttributeTargets.All)] public class HelpAttribute : System.Attribute { public readonly string Url; public string Topic // Topic 是一个命名(named)参数 { get { return topic; } set { topic = value; } } public HelpAttribute(string url) // url 是一个定位(positional)参数 { this.Url = url; } private string topic; } [HelpAttribute("Information on the class MyClass")] class MyClass { } namespace AttributeAppl { class Program { static void Main(string[] args) { System.Reflection.MemberInfo info = typeof(MyClass); object[] attributes = info.GetCustomAttributes(true); for (int i = 0; i < attributes.Length; i++) { System.Console.WriteLine(attributes[i]); } Console.ReadKey(); } } }
C# 属性(Property)
属性(Property) 是类(class)、结构(structure)和接口(interface)的命名(named)成员。 类或结构中的成员变量或方法称为 域(Field)。 属性(Property)是域(Field)的扩展,且可使用相同的语法来访问。它们使用 访问器(accessors) 让私有域的值可被读写或操作。 属性(Property)不会确定存储位置。相反,它们具有可读写或计算它们值的 访问器(accessors)。 例如,有一个名为 Student 的类,带有 age、name 和 code 的私有域。 我们不能在类的范围以外直接访问这些域,但是我们可以拥有访问这些私有域的属性Age、Name 和 Code。
访问器(Accessors)
属性(Property)的访问器(accessor)包含有助于获取(读取或计算)或设置(写入)属性的可执行语句。 访问器(accessor)声明可包含一个 get 访问器、一个 set 访问器,或者同时包含二者。 // 声明类型为 string 的 Code 属性 public string Code { get { return code; } set { code = value; } }
抽象属性(Abstract Properties)
抽象类可拥有抽象属性,这些属性应在派生类中被实现 实例 using System; namespace runoob { public abstract class Person { public abstract string Name { get; set; } public abstract int Age { get; set; } } class Student : Person { private string code = "N.A"; private string name = "N.A"; private int age = 0; // 声明类型为 string 的 Code 属性 public string Code { get { return code; } set { code = value; } } // 声明类型为 string 的 Name 属性 public override string Name { get { return name; } set { name = value; } } // 声明类型为 int 的 Age 属性 public override int Age { get { return age; } set { age = value; } } public override string ToString() { return "Code = " + Code +", Name = " + Name + ", Age = " + Age; } } class ExampleDemo { public static void Main() { // 创建一个新的 Student 对象 Student s = new Student(); // 设置 student 的 code、name 和 age s.Code = "001"; s.Name = "Zara"; s.Age = 9; Console.WriteLine("Student Info:- {0}", s); // 增加年龄 s.Age += 1; Console.WriteLine("Student Info:- {0}", s); Console.ReadKey(); } } }
抽象属性的简化版(使用c#6.0语言新特性
抽象属性例子代码的简化版(使用C# 6.0 语言新特性) using System; namespace Demo.cs { class Program { public abstract class Person { public abstract string Name { get; set; } public abstract int Age { get; set; } } public class Student : Person { public string Code { get; set; } = "N.A"; public override string Name { get; set; } = "N.A"; public override int Age { get; set; } = 0; public override string ToString() { return $"Code:{Code},Name:{Name},Age:{Age}"; } } static void Main(string[] args) { var s = new Student() { Code = "001", Name = "Zara", Age = 10 }; System.Console.WriteLine($"Student Info:={s}"); s.Age++; System.Console.WriteLine($"Student Info:={s}"); } } }
C# 索引器(Indexer)
索引器(Indexer) 允许一个对象可以像数组一样使用下标的方式来访问。 当您为类定义一个索引器时,该类的行为就会像一个 虚拟数组(virtual array) 一样。您可以使用数组访问运算符 [ ] 来访问该类的的成员。
语法
索引器定义的时候不带有名称,但带有 this 关键字,它指向对象实例。下面的实例演示了这个概念: 一维索引器的语法如下: element-type this[int index] { // get 访问器 get { // 返回 index 指定的值 } // set 访问器 set { // 设置 index 指定的值 } }
索引器(Indexer)的用途
索引器的行为的声明在某种程度上类似于属性(property)。 就像属性(property),您可使用 get 和 set 访问器来定义索引器。 但是,属性返回或设置一个特定的数据成员,而索引器返回或设置对象实例的一个特定值。 换句话说,它把实例数据分为更小的部分,并索引每个部分,获取或设置每个部分。
实例
实例 using System; namespace IndexerApplication { class IndexedNames { private string[] namelist = new string[size]; static public int size = 10; public IndexedNames() { for (int i = 0; i < size; i++) namelist[i] = "N. A."; } public string this[int index] { get { string tmp; if( index >= 0 && index <= size-1 ) { tmp = namelist[index]; } else { tmp = ""; } return ( tmp ); } set { if( index >= 0 && index <= size-1 ) { namelist[index] = value; } } } static void Main(string[] args) { IndexedNames names = new IndexedNames(); names[0] = "Zara"; names[1] = "Riz"; names[2] = "Nuha"; names[3] = "Asif"; names[4] = "Davinder"; names[5] = "Sunil"; names[6] = "Rubic"; for ( int i = 0; i < IndexedNames.size; i++ ) { Console.WriteLine(names[i]); } Console.ReadKey(); } } } 当上面的代码被编译和执行时,它会产生下列结果: Zara Riz Nuha Asif Davinder Sunil Rubic N. A. N. A. N. A.
重载索引器(Indexer)
索引器(Indexer)可被重载。索引器声明的时候也可带有多个参数,且每个参数可以是不同的类型。 没有必要让索引器必须是整型的。C# 允许索引器可以是其他类型,例如,字符串类型。
实例
实例 using System; namespace IndexerApplication { class IndexedNames { private string[] namelist = new string[size]; static public int size = 10; public IndexedNames() { for (int i = 0; i < size; i++) { namelist[i] = "N. A."; } } public string this[int index] { get { string tmp; if( index >= 0 && index <= size-1 ) { tmp = namelist[index]; } else { tmp = ""; } return ( tmp ); } set { if( index >= 0 && index <= size-1 ) { namelist[index] = value; } } } public int this[string name] { get { int index = 0; while(index < size) { if (namelist[index] == name) { return index; } index++; } return index; } } static void Main(string[] args) { IndexedNames names = new IndexedNames(); names[0] = "Zara"; names[1] = "Riz"; names[2] = "Nuha"; names[3] = "Asif"; names[4] = "Davinder"; names[5] = "Sunil"; names[6] = "Rubic"; // 使用带有 int 参数的第一个索引器 for (int i = 0; i < IndexedNames.size; i++) { Console.WriteLine(names[i]); } // 使用带有 string 参数的第二个索引器 Console.WriteLine(names["Nuha"]); Console.ReadKey(); } } } 当上面的代码被编译和执行时,它会产生下列结果: Zara Riz Nuha Asif Davinder Sunil Rubic N. A. N. A. N. A. 2
C# 委托(Delegate)
C# 中的委托(Delegate)类似于 C 或 C++ 中函数的指针。 委托(Delegate) 是存有对某个方法的引用的一种引用类型变量。引用可在运行时被改变。 委托(Delegate)特别用于实现事件和回调方法。 所有的委托(Delegate)都派生自 System.Delegate 类。
声明委托(Delegate)
委托声明决定了可由该委托引用的方法。 委托可指向一个与其具有相同标签(即返回值/型参都相同)的方法。 例如,假设有一个委托: public delegate int MyDelegate (string s); 上面的委托可被用于引用任何一个带有一个单一的 string 参数的方法,并返回一个 int 类型变量。
声明委托的语法
delegate <return type> <delegate-name> <parameter list>
实例化委托(Delegate)
一旦声明了委托类型,委托对象必须使用 new 关键字来创建,且与一个特定的方法有关。 当创建委托时,传递到 new 语句的参数就像方法调用一样书写,但是不带有参数。例如: public delegate void printString(string s); ... printString ps1 = new printString(WriteToScreen);//将对WriteToScreen方法的调用 ,委托给ps1()来完成; printString ps2 = new printString(WriteToFile); 也可以写成: printString ps1; ps1 = WriteToScreen; printString ps2 ps2 = WriteToFile; pulic void WriteToScreen(string st) { //todo; } pulic void WriteToFile(string st) { //todo; } 调用委托: ps1("我是一个字符串")//该操作实际上调用了WriteToScreen("我是一个字符串")
委托的多播(Multicasting of a Delegate)
委托对象可使用 "+" 运算符进行合并。 一个合并的委托调用它所合并的所有委托。 只有相同类型的委托可被合并。 "-" 运算符可用于从合并的委托中移除组件委托。 使用委托的这个有用的特点,您可以创建一个委托被调用时要调用的方法的调用列表。这被称为委托的 多播(multicasting),也叫组播。 组播委托按照添加的顺序依次执行.
实例1
实例 using System; delegate int NumberChanger(int n); namespace DelegateAppl { class TestDelegate { static int num = 10; public static int AddNum(int p) { num += p; return num; } public static int MultNum(int q) { num *= q; return num; } public static int getNum() { return num; } static void Main(string[] args) { // 创建委托实例 NumberChanger nc; NumberChanger nc1 = new NumberChanger(AddNum); NumberChanger nc2 = new NumberChanger(MultNum); nc = nc1; nc += nc2; // 调用多播 nc(5); Console.WriteLine("Value of Num: {0}", getNum()); Console.ReadKey(); } } } 当上面的代码被编译和执行时,它会产生下列结果: Value of Num: 75
实例2
委托多播实例:例如小明叫小张买完车票,之后接着又让他带张电影票: // 小张类 public class MrZhang { // 其实买车票的悲情人物是小张 public static void BuyTicket() { Console.WriteLine("NND,每次都让我去买票,鸡人呀!"); } public static void BuyMovieTicket() { Console.WriteLine("我去,自己泡妞,还要让我带电影票!"); } } //小明类 class MrMing { // 声明一个委托,其实就是个“命令” public delegate void BugTicketEventHandler(); public static void Main(string[] args) { // 这里就是具体阐述这个命令是干什么的,本例是MrZhang.BuyTicket“小张买车票” BugTicketEventHandler myDelegate = new BugTicketEventHandler(MrZhang.BuyTicket); myDelegate += MrZhang.BuyMovieTicket; // 这时候委托被附上了具体的方法 myDelegate(); Console.ReadKey(); } }
委托(Delegate)的用途
下面的实例演示了委托的用法。委托 printString 可用于引用带有一个字符串作为输入的方法,并不返回任何东西。 我们使用这个委托来调用两个方法,第一个把字符串打印到控制台,第二个把字符串打印到文件: 实例 using System; using System.IO; namespace DelegateAppl { class PrintString { static FileStream fs; static StreamWriter sw; // 委托声明 public delegate void printString(string s); // 该方法打印到控制台 public static void WriteToScreen(string str) { Console.WriteLine("The String is: {0}", str); } // 该方法打印到文件 public static void WriteToFile(string s) { fs = new FileStream("c:\\message.txt", FileMode.Append, FileAccess.Write); sw = new StreamWriter(fs); sw.WriteLine(s); sw.Flush(); sw.Close(); fs.Close(); } // 该方法把委托作为参数,并使用它调用方法 public static void sendString(printString ps) { ps("Hello World"); } static void Main(string[] args) { printString ps1 = new printString(WriteToScreen); printString ps2 = new printString(WriteToFile); sendString(ps1); sendString(ps2); Console.ReadKey(); } } } 当上面的代码被编译和执行时,它会产生下列结果: The String is: Hello World
C# 事件(Event)
事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息, 如系统生成的通知。 应用程序需要在事件发生时响应事件。例如,中断。 C# 中使用事件机制实现线程间的通信。 通过事件使用委托 事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。 包含事件的类用于发布事件。这被称为 发布器(publisher) 类。 其他接受该事件的类被称为 订阅器(subscriber) 类。 事件使用 发布-订阅(publisher-subscriber) 模型。 发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。 发布器(publisher)类的对象激发调用这个事件,并通知其他的对象。 订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。 在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。
声明事件(Event)
在类的内部声明事件,首先必须声明该事件的委托类型。例如: (委托一般声明在任何类之外,命名空间之下) public delegate void BoilerLogHandler(string status); 然后,在一个类的内部 声明事件本身,使用 event 关键字: // 基于上面的委托定义事件 public event BoilerLogHandler BoilerEventLog; 上面的代码定义了一个名为 BoilerLogHandler 的委托和一个名为 BoilerEventLog 的事件,该事件在生成的时候会调用委托。
实例1
实例 实例 1 明明可以很简单(对实例1的简单修改): using System; namespace SimpleEvent { /***********发布器类***********/ public class EventTest { public delegate void NumManipulationHandler(); //声明委托 public event NumManipulationHandler ChangeNum; //声明事件 public void OpenDoor() { ChangeNum(); //事件触发 } } /***********订阅器类***********/ public class subscribEvent { public void printf() { Console.WriteLine( "The door is opened." ); } } /***********触发***********/ public class MainClass { public static void Main() { EventTest e = new EventTest(); /* 实例化事件触发对象 */ subscribEvent v = new subscribEvent(); /* 实例化订阅事件对象 */ /* 订阅器的printf()在事件触发对象中注册到委托事件中 */ e.ChangeNum += new EventTest.NumManipulationHandler( v.printf ); e.OpenDoor(); /* 触发了事件 */ } } }
实例2 本实例提供一个简单的用于热水锅炉系统故障排除的应用程序。当维修工程师检查锅炉时,锅炉的温度和压力会随着维修工程师的备注自动记录到日志文件中。
实例 2 using System; using System.IO; namespace BoilerEventAppl { // boiler 类 class Boiler { private int temp; private int pressure; public Boiler(int t, int p) { temp = t; pressure = p; } public int getTemp() { return temp; } public int getPressure() { return pressure; } } // 事件发布器 class DelegateBoilerEvent { public delegate void BoilerLogHandler(string status); // 基于上面的委托定义事件 public event BoilerLogHandler BoilerEventLog; public void LogProcess() { string remarks = "O. K"; Boiler b = new Boiler(100, 12); int t = b.getTemp(); int p = b.getPressure(); if(t > 150 || t < 80 || p < 12 || p > 15) { remarks = "Need Maintenance"; } OnBoilerEventLog("Logging Info:\n"); OnBoilerEventLog("Temparature " + t + "\nPressure: " + p); OnBoilerEventLog("\nMessage: " + remarks); } protected void OnBoilerEventLog(string message) { if (BoilerEventLog != null) { BoilerEventLog(message); } } } // 该类保留写入日志文件的条款 class BoilerInfoLogger { FileStream fs; StreamWriter sw; public BoilerInfoLogger(string filename) { fs = new FileStream(filename, FileMode.Append, FileAccess.Write); sw = new StreamWriter(fs); } public void Logger(string info) { sw.WriteLine(info); } public void Close() { sw.Close(); fs.Close(); } } // 事件订阅器 public class RecordBoilerInfo { static void Logger(string info) { Console.WriteLine(info); }//end of Logger static void Main(string[] args) { BoilerInfoLogger filelog = new BoilerInfoLogger("e:\\boiler.txt"); DelegateBoilerEvent boilerEvent = new DelegateBoilerEvent(); boilerEvent.BoilerEventLog += new DelegateBoilerEvent.BoilerLogHandler(Logger); boilerEvent.BoilerEventLog += new DelegateBoilerEvent.BoilerLogHandler(filelog.Logger); boilerEvent.LogProcess(); Console.ReadLine(); filelog.Close(); }//end of main }//end of RecordBoilerInfo } 当上面的代码被编译和执行时,它会产生下列结果: Logging info: Temperature 100 Pressure 12 Message: O. K
实例3当宠物店有新狗时通知订阅客户。
一个很简单的例子:当宠物店有新狗时通知订阅客户。 using System; namespace BoilerEvent { class Dog { private String name; private String color; public delegate void Handeler(); //定义委托 public static event Handeler NewDog; //定义事件 public Dog(String Name, String Color) { name = Name; color = Color; if (NewDog != null) { Console.WriteLine("新进了一只狗"); NewDog(); //调用事件 } } } class Test { public static void Main(String[] args) { Dog d = new Dog("Tony","yellow"); //因为还没有添加订阅,所以不能触发事件 Dog.NewDog += new Dog.Handeler(Client1); //Client1添加订阅 Dog.NewDog += new Dog.Handeler(Client2);//Client2添加订阅 Dog d2 = new Dog("Tom", "white"); } static void Client1() { Console.WriteLine("我喜欢这条狗!"); } static void Client2() { Console.WriteLine("我非常想要!"); } } }
促进理解的一个实例
事件是拥有可以注册和解绑方法(函数)的功能。 虽然事件和委托看起来有点绕,只要捋清楚事件和委托的关系,就会很容易理解。 委托是一个类,事件则是委托类中的一个对象,该对象是能够把其他方法注册到委托类中的一个事件(如果觉得有点绕,可以忽略这句话)。 事件和函数的关系:事件具有可以注册多个函数(和解绑函数)的功能,而函数如果要注册和解绑其他在其主体上运行的函数则需要改动该函数本体的代码,这就是区别。 以下代码的大致流程:定义一个新类(事件类)--》类中声明委托--》由委托类又声明事件--》再定义触发事件的函数--》函数主体中执行事件--》在主函数中实例化事件类--》进而调用事件类中的事件对象--》事件对象再注册(+=)两个方法--》再执行事件类中触发事件的那个函数--》再解绑其中一个方法--》再次执行事件类中触发事件的函数。 由此可见:事件是拥有可以注册和解绑方法(函数)的功能。 using System; namespace DelegateAndEvent { //定义一个事件类 public class MyEvent { //定义一个委托 public delegate void MyDelegate(); //定义一个事件 public MyDelegate MyDelegateEvent; //定义一个触发事件的函数 public void OnMyDelegateEvent() { //判断事件是否非空 if (MyDelegateEvent != null) { //执行事件 MyDelegateEvent(); } //MyDelegateEvent?.Invoke(); //简化的判断和执行 } } class Program { //输出一串字符 public static void putOutChar() { Console.WriteLine("I was fired"); } //输出第二串字符 public static void putOutChar2() { Console.WriteLine("I was fired22222"); } static void Main(string[] args) { //实例化MyEvent2类 MyEvent myEvent = new MyEvent(); //注册一个事件 myEvent.MyDelegateEvent += new MyEvent.MyDelegate(putOutChar); myEvent.MyDelegateEvent += new MyEvent.MyDelegate(putOutChar2); //执行触发事件的函数 Console.WriteLine("执行绑定了两个事件后的函数"); myEvent.OnMyDelegateEvent(); //解绑一个事件 myEvent.MyDelegateEvent -= new MyEvent.MyDelegate(putOutChar); //再次执行触发事件的函数 Console.WriteLine("执行解绑了一个事件后的函数"); myEvent.OnMyDelegateEvent(); Console.ReadKey(); } } }
C# 集合(Collection)
集合(Collection)类是专门用于数据存储和检索的类。 这些类提供了对栈(stack)、队列(queue)、列表(list)和哈希表(hash table)的支持。大多数集合类实现了相同的接口。 集合(Collection)类服务于不同的目的,如为元素动态分配内存,基于索引访问列表项等等。这些类创建 Object 类的对象的集合。在 C# 中,Object 类是所有数据类型的基类。 类 描述和用法 动态数组(ArrayList) 它代表了可被单独索引的对象的有序集合。 它基本上可以替代一个数组。但是,与数组不同的是,您可以使用索引在指定的位置添加和移除项目,动态数组会自动重新调整它的大小。它也允许在列表中进行动态内存分配、增加、搜索、排序各项。 哈希表(Hashtable) 它使用键来访问集合中的元素。 当您使用键访问元素时,则使用哈希表,而且您可以识别一个有用的键值。哈希表中的每一项都有一个键/值对。键用于访问集合中的项目。 排序列表(SortedList) 它可以使用键和索引来访问列表中的项。 排序列表是数组和哈希表的组合。它包含一个可使用键或索引访问各项的列表。如果您使用索引访问各项,则它是一个动态数组(ArrayList),如果您使用键访问各项,则它是一个哈希表(Hashtable)。集合中的各项总是按键值排序。 堆栈(Stack) 它代表了一个后进先出的对象集合。 当您需要对各项进行后进先出的访问时,则使用堆栈。当您在列表中添加一项,称为推入元素,当您从列表中移除一项时,称为弹出元素。 队列(Queue) 它代表了一个先进先出的对象集合。 当您需要对各项进行先进先出的访问时,则使用队列。当您在列表中添加一项,称为入队,当您从列表中移除一项时,称为出队。 点阵列(BitArray) 它代表了一个使用值 1 和 0 来表示的二进制数组。 当您需要存储位,但是事先不知道位数时,则使用点阵列。您可以使用整型索引从点阵列集合中访问各项,索引从零开始。
C# 泛型(Generic)
泛型(Generic) 允许您延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候。 换句话说,泛型允许您编写一个可以与任何数据类型一起工作的类或方法。 您可以通过数据类型的替代参数编写类或方法的规范。 当编译器遇到类的构造函数或方法的函数调用时,它会生成代码来处理指定的数据类型。
泛型(Generic)的特性
使用泛型是一种增强程序功能的技术,具体表现在以下几个方面: 它有助于您最大限度地重用代码、保护类型的安全以及提高性能。 您可以创建泛型集合类。.NET 框架类库在 System.Collections.Generic 命名空间中包含了一些新的泛型集合类。您可以使用这些泛型集合类来替代 System.Collections 中的集合类。 您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。 您可以对泛型类进行约束以访问特定数据类型的方法。 关于泛型数据类型中使用的类型的信息可在运行时通过使用反射获取。
泛型(Generic)方法
可以通过类型参数声明泛型方法。 在声明泛型方法/泛型类的时候,可以给泛型加上一定的约束来满足我们特定的一些条件。 比如: using System; using System.Web.Caching; namespace Demo.CacheManager { public class CacheHelper<T> where T:new() { } } 泛型限定条件: T:结构(类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型) T:类 (类型参数必须是引用类型,包括任何类、接口、委托或数组类型) T:new() (类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时new() 约束必须最后指定) T:<基类名> 类型参数必须是指定的基类或派生自指定的基类 T:<接口名称> 类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。 T:U 在封装公共组件的时候,很多时候我们的类/方法不需要关注调用者传递的实体是"什么",这个时候就可以使用泛型。 比如: using System; using System.Web.Caching; namespace Xy.CacheManager { public class CacheHelper<T> { //获取缓存实体 public static T Get(Cache cache,string cacheKey) { //....缓存操作 } //插入缓存 public static void Set(Cache cache T tEntity,string cacheKey) { //....缓存操作 } } }
泛型(Generic)委托
您可以通过类型参数定义泛型委托。例如: delegate T NumberChanger<T>(T n);
C# 匿名方法
匿名方法(Anonymous methods) 提供了一种传递代码块作为委托参数的技术。匿名方法是没有名称只有主体的方法。 在匿名方法中您不需要指定返回类型,它是从方法主体内的 return 语句推断的。
编写匿名方法的语法
匿名方法是通过使用 delegate 关键字创建委托实例来声明的。例如: delegate void NumberChanger(int n); ... NumberChanger nc = delegate(int x) { Console.WriteLine("Anonymous Method: {0}", x); }; 代码块 Console.WriteLine("Anonymous Method: {0}", x); 是匿名方法的主体。 委托可以通过匿名方法调用,也可以通过命名方法调用,即,通过向委托对象传递方法参数。 注意: 匿名方法的主体后面需要一个 ;。 例如: nc(10);
实例
实例 下面的实例演示了匿名方法的概念: 实例 using System; delegate void NumberChanger(int n); namespace DelegateAppl { class TestDelegate { static int num = 10; public static void AddNum(int p) { num += p; Console.WriteLine("Named Method: {0}", num); } public static void MultNum(int q) { num *= q; Console.WriteLine("Named Method: {0}", num); } static void Main(string[] args) { // 使用匿名方法创建委托实例 NumberChanger nc = delegate(int x) { Console.WriteLine("Anonymous Method: {0}", x); }; // 使用匿名方法调用委托 nc(10); // 使用命名方法实例化委托 nc = new NumberChanger(AddNum); // 使用命名方法调用委托 nc(5); // 使用另一个命名方法实例化委托 nc = new NumberChanger(MultNum); // 使用命名方法调用委托 nc(2); Console.ReadKey(); } } } 当上面的代码被编译和执行时,它会产生下列结果: Anonymous Method: 10 Named Method: 15 Named Method: 30
C# 不安全代码
C# 多线程