特性与反射
特性(Attribute)
特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。您可以通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所应用的元素前面的方括号([ ])来描述的。
特性(Attribute)用于添加元数据,如编译器指令和注释、描述、方法、类等其他信息。.Net 框架提供了两种类型的特性:预定义特性和自定义特性。
Attribute是一种可由用户自有定义的修饰符(Modifier),可以用来修饰各种需要被修饰的目标。我们可以对类、以及C#程序集中的成员进行进一步的描述。
简单地说,Attribute就是一种“附着物”——就像牡蛎吸附在船底或礁石上一样。 这些附着物的作用是为它们的附着体追加上一些额外的信息(这些信息保存在附着物的体内)——比如“这个类是我写的”或者“这个函数以前出过问题”等等
Attribute与注释的区别
注释是对程序源代码的一种说明,主要目的是给人看的,在程序被编译的时候会被编译器所丢弃,因此,它丝毫不会影响到程序的执行。
Attribute是程序代码的一部分,它不但不会被编译器丢弃,而且还会被编译器编译进程序集(Assembly)的元数据(Metadata)里。在程序运行的时候,随时可以从元数据(元数据:.NET中元数据是指程序集中的命名空间、类、方法、属性等信息,这些信息是可以通过Reflection读取出来的。)中提取提取出这些附加信息,并以之决策程序的运行。
规定特性(Attribute)
特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集,它可以放置在几乎所有的声明中(但特定的属性可能限制在其上有效的声明类型)。其语法为:
- 在结构前放置特性片段来运用特性
- 特性片段被方括号包围,其中是特性名和特性的参数列表
例:
// 不含参数的特性
[Serializable]
public class MyClass{...}
// 带有参数的特性
[MyAttribute("firt","second","finally")]
public class MyClass {...}
单个结构可以运用多个特性,使用时可以把独立的特性片段互相叠在一起或使用分成单个特性片段,特性之间用逗号分隔
// 独立的特性片段
[Serializable]
[MyAttribute("firt","second","finally")]
public class MyClass {...}
// 逗号分隔
[MyAttribute("firt","second","finally"), Serializable]
public class MyClass {...}
某些属性对于给定实体可以指定多次。例如,Conditional 就是一个可多次使用的属性:
[Conditional("DEBUG"), Conditional("TEST1")]
void TraceMethod()
{
// ...
}
预定义特性(Attribute)
.Net 框架提供了三种预定义特性:
- AttributeUsage
- Conditional
- Obsolete
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。例如,当调试代码时显示变量的值。
#define CONDITIONA
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Text9
{
class Program
{
static void Main(string[] args)
{
Test1();
Test2();
}
[Conditional("CONDITIONA")]
public static void Test1()
{
Console.WriteLine("Test1");
}
public static void Test2()
{
Console.WriteLine("Test2");
}
}
}
Obsolete
这个预定义特性标记了不应被使用的程序实体。它可以让您通知编译器丢弃某个特定的目标元素。例如,当一个新方法被用在一个类中,但是您仍然想要保持类中的旧方法,您可以通过显示一个应该使用新方法,而不是旧方法的消息,来把它标记为 obsolete(过时的)。
规定该特性的语法如下:
[Obsolete(
message
)]
[Obsolete(
message,
iserror
)]
其中:
- 参数 message,是一个字符串,描述项目为什么过时以及该替代使用什么。
- 参数 iserror,是一个布尔值。如果该值为 true,编译器应把该项目的使用当作一个错误。默认值是 false(编译器生成一个警告)。
下面的实例演示了该特性:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Text9
{
class Program
{
[Obsolete("这个方法过时了,用NowMethod代替",true)]//用来表示一个方法被弃用
static void OldMethod()
{
Console.WriteLine("OldMethod");
}
static void NowMethod()
{
Console.WriteLine("NowMethod");
}
static void Main(string[] args)
{
OldMethod();
}
}
}
创建自定义特性(Attribute)
.Net 框架允许创建自定义特性,用于存储声明性的信息,且可在运行时被检索。该信息根据设计标准和应用程序需要,可与任何目标元素相关。
创建并使用自定义特性包含四个步骤:
- 声明自定义特性
- 构建自定义特性
- 在目标程序元素上应用自定义特性
- 通过反射访问特性
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 声明自定义特性
{
internal class Program
{
static void Main(string[] args)
{
}
}
// 1、创建一个自定义特性:
// 描述如何使用一个自定义特性 SomethingAttribute
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]
//********自定义特性SomethingAttribute**************//
public class SomethingAttribute : Attribute
{
private string name; // 名字
private string data; // 日期
public string Name
{
get { return name; }
set { name = value; }
}
public string Data
{
get { return data; }
set { data = value; }
}
public SomethingAttribute(string name)
{
this.name = name;
this.name = name;
}
}
//2.实例化自定义
[Something("Amy", Data = "2018-06-18")]
[Something("Jack", Data = "2018-06-18")]
class Test { }
}
反射(Reflection)
反射指程序可以访问、检测和修改它本身状态或行为的一种能力。
程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。
可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。
优缺点
优点:
- 反射提高了程序的灵活性和扩展性。
- 降低耦合性,提高自适应能力。
- 它允许程序创建和控制任何类的对象,无需提前硬编码目标类。
缺点:
- 性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。
- 使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。
反射(Reflection)的用途
反射(Reflection)有下列用途:
- 它允许在运行时查看特性(attribute)信息。
- 它允许审查集合中的各种类型,以及实例化这些类型。
- 它允许延迟绑定的方法和属性(property)。
- 它允许在运行时创建新类型,然后使用这些类型执行一些任务。
查看元数据
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 声明自定义特性
{
internal class Program
{
static void Main(string[] args)
{
//3、获取自定义特性的中的变量
Type t = typeof(Test);
var something = t.GetCustomAttributes(typeof(SomethingAttribute), true);
foreach (SomethingAttribute each in something)
{
Console.WriteLine("Name:{0}", each.Name);
Console.WriteLine("Data:{0}", each.Data);
}
}
}
// 1、创建一个自定义特性:
// 描述如何使用一个自定义特性 SomethingAttribute
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]
//********自定义特性SomethingAttribute**************//
public class SomethingAttribute : Attribute
{
private string name; // 名字
private string data; // 日期
public string Name
{
get { return name; }
set { name = value; }
}
public string Data
{
get { return data; }
set { data = value; }
}
public SomethingAttribute(string name)
{
this.name = name;
this.name = name;
}
}
//2.实例化自定义
[Something("Amy", Data = "2018-06-18")]
[Something("Jack", Data = "2018-06-18")]
class Test { }
}