什么是元数据和反射???
1.程序是用来处理数据的,文本和特性都是数据,而我们程序本身(类的定义和BLC中的类)这些也是数据。
2.有关程序及其类型的数据被称为元数据(metadata),它们保存在程序的程序集中。
程序在运行时,可以查看其它程序集或其本身的元数据。一个运行的程序查看本身的元数据或者其他程序集的元数据的行为叫做反射。
下面我们我们来学习如何使用Type类来反射数据,以及如何使用特性来给类型添加元数据。
Type位于System.Reflection命名空间下
Type类:
预定义类型(int long 和string等),BCL中的类型(Console,IEnumerable等)和程序员自定义类型(MyClass,MyDel等)。 每种类型都有自己的成员和特性。
BCL声明了一个叫做Type的抽象类,它被设计用来包含类型的特性。使用这个类的对象能让我们获取程序使用的类型的信息。
由于 Type是抽象类,因此不能利用它去实例化对象。关于Type的重要事项如下:
对于程序中用到的每一个类型,CLR都会创建一个包含这个类型信息的Type类型的对象。
程序中用到的每一个类型都会关联到独立的Type类的对象。
不管创建的类型有多少个示例,只有一个Type对象会关联到所有这些实例。
Type是使用示例:
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
namespace _002_反射
{
class Program
{
static void Main(string[] args)
{
//每一个类对应一个type对象,,这个对象存储了这个类,有哪些方法,成员
Text mytext = new Text(); //类中的数据是存储在对象中的,type之存储类的成员
//通过对象获取对象所属类的Type的对象
Type type = mytext.GetType();
Console.WriteLine(type.Name); //获取类名
Console.WriteLine(type.Namespace); //所在命名空间
Console.WriteLine(type.Assembly); //程序集
//获取方法中所有公有的(public)字段
Console.WriteLine();
Console.WriteLine("========公有字段=========");
FieldInfo[] infoarr = type.GetFields();
foreach (FieldInfo item in infoarr)
{
Console.Write(item.Name + " ");
}
//获取所有的公有属性
Console.WriteLine();
Console.WriteLine("========公有属性=========");
PropertyInfo[] properarr = type.GetProperties();
foreach (PropertyInfo info in properarr)
{
Console.Write(info.Name + " ");
}
//获取所有的公有方法
Console.WriteLine();
Console.WriteLine("========公有方法=========");
MethodInfo[] methodarr = type.GetMethods();
foreach (MethodInfo info in methodarr)
{
Console.Write(info.Name + " ");
}
Console.ReadKey();
}
}
}
测试用的Text类
namespace _002_反射
{
class Text
{
private int id;
public int age;
public int num;
public string Name { get; set; }
public string Age { get; set; }
public string Sex { get; set; }
public void Test1() {}
public void Text2() { }
}
}
运行结果图:
Assembly 类: (Text类使用的还是上面的那个)
如何加载程序集?
1,Assembly assembly1 = Assembly.Load(“SomeAssembly”);根据程序集的名字加载程序集,它会在本地目录和全局程序集缓存目录查找符合名字的程序集。
2,Assembly assembly2 = Assembly.LoadFrom(@”c:\xx\xx\xx\SomeAssembly.dll”)//这里的参数是程序集的完整路径名,它不会在其他位置搜索。
using System;
using System.Reflection;
namespace _002_反射
{
class Program
{
static void Main(string[] args)
{
Text mytext = new Text();
//通过类的type对象获取它所在的程序集
Assembly assem = mytext.GetType().Assembly;
Console.WriteLine("完整的名字:"+ assem.FullName);
Type[] type = assem.GetTypes();
foreach (var item in type)
{
Console.WriteLine(item);
}
//源码编译就会出来.exe文件和一些配置文件,即程序集,,
Console.ReadKey();
}
}
}
特性:
特性(attribute)是一种允许我们向程序的程序集增加元数据的语言结构。它是用于保存程序结构信息的某种特殊类型的类。
将应用了特性的程序结构叫做目标
设计用来获取和使用元数据的程序(对象浏览器)叫做特性的消费者
.NET预定了很多特性,我们也可以声明自定义特性
[ Obsolete]示例
namespace _003_特性
{
class Program
{
static void Main(string[] args)
{
// 当 [Obsolete ("此方法已过时,请使用NewFun"),ture] 调用会出编译错误
// OldFun();
//若不写第二个参数,,则会画波浪线做出提示,且方法可用
OldFun();
}
//通过这个特性表示一个方法已经被弃用了,(但是还能使用下面会有波浪线做标识)
[Obsolete ("此方法已过时,请使用NewFun")]
static void OldFun()
{
Console.WriteLine("OldFun");
}
static void NewFun()
{
Console.WriteLine("NewFun");
}
}
}
Conditional特性:
Conditional特性允许我们包括或取消特定方法的所有调用。为方法声明应用Conditional特性并把编译符作为参数来使用。
定义方法的CIL代码本身总是会包含在程序集中,只是调用代码会被插入或忽略。
应用示例:
#define Method1 //定义一个宏,控制Method1()是否被调用,若不存在则所有的Method1() 都不会调用
using System;
using System.Diagnostics; //命名空间
namespace _003_特性
{
class Program
{
static void Main(string[] args)
{
//模拟多次调用方法,,,如果想取消调用method1() 就会变得麻烦
Method1();
Method2();
Method1();
Console.ReadKey();
}
[Conditional("Method1")]
static void Method1()
{
Console.WriteLine("Method1");
}
static void Method2()
{
Console.WriteLine("Method2");
}
}
}
调用者信特性:
调用者信息特性可以访问文件路径,代码行数,调用成员的名称等源代码信息。
1.这个三个特性名称为CallerFilePath,CallerLineNumber和CallerMemberName
2.这些特性只能用于方法中的可选参数
using System;
using System.Runtime.CompilerServices;
namespace _003_特性
{
class Program
{
static void Main(string[] args)
{
DebugLog("Czhenya");
Console.ReadKey();
}
static void DebugLog(string str,[CallerFilePath]string fileName = "",
[CallerLineNumber]int lineNumber = 0,
[CallerMemberName]string methodName = "")
{
Console.WriteLine("用户输入的参数:"+str);
Console.WriteLine("调用的目录:"+fileName);
Console.WriteLine("在第{0}行调用的",lineNumber);
Console.WriteLine("在{0}方法中调用的",methodName);
}
}
}
DebuggerStepThrough 特性
单步调试代码的时候,常常希望调试器不要进入某些方法。我们只想执行该方法,然后继续调试下一行。DebuggerStepThrough特性告诉调试器在执行目标代码时不要进入该方法调试。有些方法小并且毫无疑问是正确的,在调试时对其反复单步调试只能徒增烦恼。要小心使用该特性,不要排除了可能出现bug的代码。
[DebuggerStepThrough] //跳过这个方法的但不测试,当确定此方法无任何错误的时候可以使用,,,
自定义特性类:
声明一个特性类和声明其他类一样。有下面的注意事项
声明一个派生自System.Attribute的类
给它起一个以后缀Attribute结尾的名字
(安全起见,一般我们声明一个sealed的特性类)
特性类声明如下:
public sealed class MyAttribute : System.Attribute{
…
特性类的公共成员可以是
字段
属性
构造函数
AttributeTarget枚举的成员
All Assembly Class Constructor Delegate Enum Event Field
GenericParameter Interface Method Module Parameter Property ReturnValue Struct
多个参数之间使用按位或|运算符来组合
AttributeTarget.Method|AttributeTarget.Constructor
自定义特性类一般遵循的规则:
特性类应该表示目标结构的一些状态
如果特性需要某些字段,可以通过包含具有位置参数的构造函数来收集数据,可选字段可以采用命名参数按需初始化
除了属性之外,不要实现公共方法和其他函数成员
为了更安全,把特性类声明为sealed
在特性声明中使用AttributeUsage来指定特性目标组
自
using System;
namespace _003_特性
{
// 特性类,后缀以Attribute结尾
// 需要继承自System.Attibute
// 一般声明为sealed
// 一般情况下特性类用来表示目标结构的一些状态,定义一些字段,属性,不定义方法
[AttributeUsage(AttributeTargets.Class)] //表示该特性类可以使用的结构有哪些
sealed class MyAttribute : Attribute
{
public string Desctiption { get; set; }
public string VersionNumber { get; set; }
public int ID { get; set; }
//构造函数
public MyAttribute(string str)
{
this.Desctiption = str;
}
}
}
using System;
namespace _003_特性
{
//通过制定属性的名字,给属性赋值,这种就是命名参数
[My("自定义的特性类", ID=111)] //使用特性的时候,后面的Attribute不用写
class Program
{
static void Main(string[] args)
{
//通过typeof(类名) 也可创建type对象
Type type = typeof(Program);
object[] objarr = type.GetCustomAttributes(false);
//获取本类使用到的特性
MyAttribute my = objarr[0] as MyAttribute;
Console.WriteLine(my.Desctiption+"\n ID : "+my.ID);
Console.ReadKey();
}
}
}