c#特性与反射
特性
定义:在程序运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)行为信息的声明性标签。
看定义,一脸懵逼,那这玩意究竟有什么用呢?
举个例子:比如我们有一个开发平台,向外部提供了API;然后,当我们更新时,想使用一个新的方法代替其中一个方法;这时我们总不能直接把老方法删了吧,但是我们又不想让人家用老方法了,怎么办呢?这时候,特性就可以帮助我们在人家调用老方法时提醒人。怎么用呢?呐,示例代码:
| 1 | [Obsolete("Don't use OldMethod(), use NewMethod()", false)] | 
Obsolete是.Net提供的三个预定义特性之一,这个预定义特性标记了不应该被使用的程序实体。它可以让您通知编译器丢弃某个特定的目标元素。语法为:[Obsolete(message,iserror)]   
- message:字符串参数,描述项目实体过时的原因以及该使用什么替代。
- iserror,布尔参数。为 true 时,编译器把对该实体的使用当作一个错误。默认值是 false(当做警告)。
.Net提供了两种类型的特性:预定义特性,自定义特性。
个人理解:预定义特性是在预处理阶段起作用的特性。
预定义特性
除了Obsolete,另外两个预定义特性是AttributeUsage与Conditional。
AttributeUsage
语法如下:
| 1 | [AttributeUsage( | 
这位的作用是描述了如何使用一个自定义特性。可以理解成用它来约束我们编写的自定义特性。看参数就知道了:
- validon:规定特性可“修饰”的语言元素(类、方法…)。它是枚举器 AttributeTargets 的值的组合,使用OR(|)运算法组合;默认值是 AttributeTargets.All。
- allowmultiple:为该特性的 AllowMultiple 属性提供一个布尔值。为 true 时该特性是多用的( 即我们的定制特性能被重复放置在同一个程序实体(语言元素)前多次。)。默认值是 false(单用的)
- inherited:为该特性的 Inherited 属性提供一个布尔值。为 true 时该特性可被派生类继承。默认值是 false(不被继承);这里的继承指当被这个特性“修饰”的类被继承时,其子类是否也继承了此特性。
 示例代码:
| 1 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, | 
上述代码中,我们自定义了一个特性MyHelpAttribure,并用预定义特性AttributeUsage约束它只能放置在类和方法前面,可重复放置,可被继承。(自定义特性其实就是一个继承了System.Attribute类的类)
Conditional
Conditional 在命名空间 System.Diagnostics 下,语法如下:[Conditional(conditionalSymbol)]   
- conditionalSymbol:字符串类型,为预处理标识符的名称。
 这个特性只能放置在特性和方法之前。 这个预定义特性标记了一个条件方法,其执行依赖于指定的预处理标识符,它会引起方法调用的条件编译。 和预编译指令 #if、#endif 作用相同。不同的是#if、#endif作用于代码段,Conditional作用于方法。
 示例代码:
| 1 | using System; | 
上面的代码在Debug环境里执行结果为:
Method1
Method2
在Release环境里执行结果为:
Method2
因为在Debug环境下定义的有 预处理标识符 DEBUG。
自定义特性
前面说过,自定义特性就是一个继承了System.Attribute的类。一般我们将自定义特性用于存储声明性的信息,可在运行时通过反射技术检索这些信息。
下面,通过一个示例来了解一下自定义特性。
反射
定义:反射提供描述程序集、模块和类型的对象(Type 类型)。 可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型,然后调用其方法或访问其字段和属性。 如果代码中使用了特性,可以利用反射来访问它们。
通俗点讲,反射能够在获得对象的内部结构。可以理解成“给一个对象做b超”(这个对象是 Type 类型的对象实例,不是人啊,别想多了)。
在此之前,要了解一些名词。   
- 程序集、模块、与类型。
 .Net应用程序由“程序集”,“模块”,“类型”组成,程序集包含模块,模块包含类型。
- 命名空间与装配件的关系。
 名空间类似与Java的包,但又不完全等同,因为Java的包必须按照目录结构来放置,命名空间则不需要。
 装配件,是.Net应用程序执行的最小单位,编译出来的.dll、.exe都是一个装配件。一个程序集中可以有无数个名称不同的命名空间,不同程序集之间可以有名字相同的命名空间。
 关于装配件与程序集,是同一个概念(都是翻译惹得祸吧,英文:Assembly),这个不用纠结ヽ(ー_ー)ノ。
以上名词来自网络资料,如有不对,再做更改
System.Type 类是反射的中心。 当反射提出请求时,公共语言运行时为已加载的类型创建 Type。 可使用 Type 对象的方法、字段、属性和嵌套类来查找该类型的任何信息。
继承关系:Object->MemberInfo->Type
- 获取对象的三种方式
| 1 | Type t = typeof(String); | 
使用 Assembly.GetType 或 Assembly.GetTypes 从尚未加载的程序集中获取 Type 对象,传入所需类型的名称。 使用 Type.GetType 从已加载的程序集中获取 Type 对象。 使用 Module.GetType 和 Module.GetTypes 获取模块 Type 对象。
- 如何使用反射访问特性 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72- //自定义特性类 
 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
 class DebugInfo : Attribute
 {
 private int bugNo; //调错编号
 private string lastReview; //最后一次排错时间
 private string message; //描述
 public DebugInfo(int iNo, String sLR, String sMsg)
 {
 bugNo = iNo;
 lastReview = sLR;
 message = sMsg;
 }
 public int BugNo
 {
 get { return bugNo; }
 }
 public String LastReview
 {
 get { return lastReview; }
 }
 public String Message
 {
 get { return message; }
 }
 }
 [DebugInfo(17, "22/04/2019", "fx")]
 [DebugInfo(16, "18/04/2019", "fx")]
 class Test
 {
 [DebugInfo(17, "22/04/2019", "fx")]
 void Method1()
 {
 }
 [DebugInfo(16, "18/04/2019", "fx")]
 public void Method2()
 {
 }
 }
 class Program
 {
 static void Main(string[] args)
 {
 Type type = typeof(Test);
 ///1、遍历类Test的特性
 foreach (Object obj in type.GetCustomAttributes(false))
 {
 DebugInfo dbInfo = (DebugInfo)obj;
 if(dbInfo != null)
 {
 Console.WriteLine("bug no: {0}", dbInfo.BugNo);
 Console.WriteLine("last review: {0}", dbInfo.LastReview);
 Console.WriteLine("message: {0}", dbInfo.Message);
 }
 }
 ///2、遍历Test类中方法的特性
 foreach(MethodInfo mi in type.GetMethods())
 {
 foreach (Object obj in mi.GetCustomAttributes(false))
 {
 DebugInfo dbInfo = (DebugInfo)obj;
 if (dbInfo != null)
 {
 Console.WriteLine("bug no: {0}", dbInfo.BugNo);
 Console.WriteLine("last review: {0}", dbInfo.LastReview);
 Console.WriteLine("message: {0}", dbInfo.Message);
 }
 }
 }
 Console.ReadKey();
 }
 }
- 使用反射查看类型信息 
| 1 | class Program | 
- 使用反射获取及调用构造方法
| 1 | class Test | 
BindingFlags是一个枚举类型,常用枚举值如下:
- BindingFlags.Public : 指定公开成员将被包含在搜索中
- BindingFlags.Static :指定静态成员将被包含在搜索中
- BindingFlags.NonPublic:指定非公开成员将被包含在搜索中
- BindingFlags.Instance :指定实例成员将被包含在搜索中
其它(方法、字段、属性等)的操作与此类似:
- MemberInfo类是用来获取所有成员 (构造函数、 事件、 字段、 方法和属性) 的类的相关信息的类的抽象基类。
- 使用Assembly定义和加载程序集,加载在程序集清单中列出模块,以及从此程序集中查找类型并创建该类型的实例。
- 使用Module了解包含模块的程序集以及模块中的类等,还可以获取在模块上定义的所有全局方法或其他特定的非全局方法。
- 使用ConstructorInfo了解构造函数的名称、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。
- 使用MethodInfo了解方法的名称、返回类型、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。
- 使用FiedInfo了解字段的名称、访问修饰符(如public或private)和实现详细信息(如static)等,并获取或设置字段值。
- 使用EventInfo了解事件的名称、事件处理程序数据类型、自定义属性、声明类型和反射类型等,添加或移除事件处理程序。
- 使用PropertyInfo了解属性的名称、数据类型、声明类型、反射类型和只读或可写状态等,获取或设置属性值。
- 使用ParameterInfo了解参数的名称、数据类型、是输入参数还是输出参数,以及参数在方法签名中的位置等。
反射的优缺点:
- 优点: - 反射提高了程序的灵活性和扩展性。
- 降低耦合性,提高自适应能力。
- 它允许程序创建和控制任何类的对象,无需提前硬编码目标类。
 
- 缺点: - 性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。
- 使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。
 
关于反射的使用,这里只做简单的介绍,深入学习的话请访问反射(c#)编程指南.
c#属性(Property)
定义:属性是一种成员,它提供灵活的机制来读取、写入或计算私有字段的值。 属性可用作公共数据成员,但它们实际上是称为访问器的特殊方法。 这使得可以轻松访问数据,还有助于提高方法的安全性和灵活性。
访问器包括get和set,通过get来返回某个字段的值,在set中,使用关键字value为某个字段进行赋值。属性中至少有一个访问器,可以只有一个get或set访问器,或者既有get又有set访问器。
可以用访问限制修饰符修饰访问器,不过限制级别要大于属性的限制修饰符。   
- 限制级别:private > internal和protected > public
| 1 | class Test | 
在一些情况下,属性 get 和 set 访问器仅为支持字段赋值或仅从其中检索值,而没有其它附加逻辑。 这是,通过使用自动实现的属性,既能简化代码,还能让 C# 编译器透明地提供支持字段(即我们不用显示声明字段了)。
| 1 | class Test | 
深入学习请访问:属性(c#)
索引器
定义:索引器允许类或结构的实例像数组一样进行索引。 无需显式指定类型或实例成员,即可设置或检索索引值。 类似于属性,不同之处在于索引器的的访问器需要使用参数。
下面是一个简单索引器的使用案例:
| 1 | class collection | 
当然,我们可以在泛型类中来使用索引器。
| 1 | class SampleCollection<T> | 
索引器也可以被重载。
| 1 | class collection | 
关于更多请访问索引器(c#)
c#委托与事件
委托
定义:委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。 在实例化委托时,你可以将其实例与任何具有兼容参数和返回类型的方法相关联。 并能通过委托实例调用方法。
委托具有以下属性:
- 委托类似于 C++ 函数指针,但委托完全面向对象,不像 C++ 指针会记住函数,委托会同时封装对象 + 实例和方法。
- 委托允许将方法作为参数进行传递。
- 委托可用于定义回调方法。
- 委托可以链接在一起;例如,可以对一个事件调用多个方法。
声明一个委托:
| 1 | public delegate int DgName(int para1, string para2); | 
实例化委托并简单使用:
| 1 | class Program | 
上面说过,委托允许将方法作为参数进行传递,因此,我们还可以这样使用委托。
| 1 | class PrintString | 
委托对象可使用加法赋值运算符(“+”或“+=”)进行合并。一个合并委托调用它所合并的两个委托。只有相同类型的委托可被合并。
使用委托的这个有用的特点,您可以创建一个委托被调用时要调用的方法的调用列表。这被称为委托的多播(multicasting),也叫组播。 若要删除调用列表中的方法,使用减法运算符或减法赋值运算符(“-”或“-=”)。
| 1 | class Program | 
更多请访问委托(c#)
事件
定义:类或对象可以通过事件向其他类或对象通知发生的相关事情。 发送(或 引发)事件的类称为“发行者(发布器)” ,接收(或 处理)事件的类称为“订户(订阅器)” 。
- 发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器类的对象调用这个事件,并通知其他的对象。
- 订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器类中的方法(事件处理程序)。
由上面的定义可以看出,事件,实际上就是.Net对观察这模式的应用。
在类中声明事件之前,必须要先声明用于该事件的委托。例:
| 1 | public delegate void Cry(); | 
编写一个实例:当老鼠跑时,狗叫,猫叫并追赶老鼠;因为在这个设计模型中,当老鼠跑时要通知猫追赶它,通知狗叫,因此老鼠类就是“发布器”,狗类和猫类就是“订阅器”。
| 1 | using System; | 
跟多关于事件(c#)
 
   
          
         
          
        