前言

  在本节中主要讲述自定义特性、反射。自定义特性允许把自定义元数据与程序元素关联起来。这些元数据是在编译过程中创建的,并嵌入程序集中。反射是一个普通的术语,它描述了在运行过程中检查和处理程序元素的功能。例如,反射运行完成以下任务:

  • 枚举类型的成员
  • 实例化新对象
  • 执行对象的成员
  • 查找类型的信息
  •  查找程序集的信息
  • 检查应用于某个类型的自定义特性
  • 创建和编译新程序集

这个列表列出了许多功能,本章中主要介绍部分常用的功能。

自定义特性

一、编写自定义特性

  1. 理解自定义特性

[LastModified("Test","Test")]
public class TestNumber { }

  这个例子首先会发现LastModified这个特性,首先把字符串Attribute追加到这个名称后面,形成一个组合LastModifiedAttribute,然后在其搜多路径的所有名称空间去搜索这个名称的类。注意如果本来就以Attribute结尾了,那么也就不会组合在一起了。编译器会找到含有改名称的类,且这个类直接或间接派生自System.Attribute编译器很认为这个类包含控制特性用法的信息。特别是属性类需要指定:

  • 特性可以应用到那些类型的程序元素上(类、结构、属性和方法等)
  • 是否可以多次应用到同一个应用程序元素上
  • 在应用到类和接口上时,是否由派生类和接口继承
  • 这个特性有那些必选和可选参数

  如果哦编译器找不到对应的特性类,或者找到了但是使用方式或者信息不对,编译器就会产生一个编译错误。

  下面我们看看自定义特性其中的各个元素如何定义吧

  2. 指定AttributeUsage特性

  第一个要注意的就是AttributeUsage特性,它是特性类的标记。AttributeUsage主要用于标识自定义特性可以应用到那些类型的程序元素上。 这些信息都是由第一个参数提供的,该参数输入必选参数,其类型是枚举类型AttributeTargets。其成员如下:

All

32767

可以对任何应用程序元素应用属性。

Assembly

1

可以对程序集应用属性。

Class

4

可以对类应用属性。

Constructor

32

可以对构造函数应用属性。

Delegate

4096

可以对委托应用属性。

Enum

16

可以对枚举应用属性。

Event

512

可以对事件应用属性。

Field

256

可以对字段应用属性。

GenericParameter

16384

可以对泛型参数应用属性。 目前,此属性仅可应用于 C#、Microsoft 中间语言 (MSIL) 和已发出的代码中。

Interface

1024

可以对接口应用属性。

Method

64

可以对方法应用属性。

Module

2

可以对模块应用属性。 Module 引用的是可移植可执行文件(.dll 或 .exe),而不是 Visual Basic 标准模块。

Parameter

2048

可以对参数应用属性。

Property

128

可以对属性 (Property) 应用属性 (Attribute)。

ReturnValue

8192

可以对返回值应用属性。

Struct

8

可以对结构应用属性,即值类型。

  在上面列表中,有两个值不对应于任何程序元素:Assembly和Module。特性可以应用到整个程序集或模块中,而不是应用到代码中的一个元素上,在这种情况下,这个特性可以放在源代码的任何地方,但需要关键字Assembly和Module作为前缀

[assembly:SupportsWhatsNew]
[module: SupportsWhatsNew]

  下面我们再介绍几个参数AllowMultiple表示一个特性是否可以多次应用到同一项,Inherited表示应用到类或接口上的特性是否可以自动应用到所以的派生的类或接口上。如果特性应用到方法或者属性上,就表示是否可以自动应用到该方法或属性等的重新版本上。

二、自定义特性示例

  经过上面的介绍,下面我们开始定义自定义特性示例。这里我们将创建两个类库,第一个WhatsNewAttributes库程序集,其中定义了两个特性,LastModifiedAttribute和SupportsWhatsNewAttribute。

  LastModifiedAttribute特性可以用于标记最后一次修改数据项的时间,它有两个必选参数:修改的日期和包含描述修改的信息。还有一个可选参数issues,它可以用来描述该数据项的任何重要问题。

  SupportsWhatsNewAttribute是一个较小的类,不带有任何参数的特性。这个特性是一个程序集的特性,用于把程序集标记为通过SupportsWhatsNewAttribute维护的文档。

   /// <summary>
/// 用于标记最后一次修改数据项的时间和信息。
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Constructor,
AllowMultiple = true, Inherited = false)]
public class LastModifiedAttribute : Attribute
{
private readonly DateTime _dateModified;
private readonly string _changes;
public LastModifiedAttribute(string dateModified, string changes)
{
_dateModified = DateTime.Parse(dateModified);
_changes = changes;
}
public DateTime DateModified => _dateModified;
public string Changes => _changes;
public string Issues { get; set; }
} /// <summary>
/// 用于把程序集标记为通过LastModifiedAttribute维护的文档
/// </summary>
[AttributeUsage(AttributeTargets.Assembly)]
public class SupportsWhatsNewAttribute : Attribute
{ }

  接下来我们介绍第二个库VectorClass。VectorClass库引用了WhatsNewAttributes库,添加声明后我们使用全局程序集特性标记程序集。

[assembly:SupportsWhatsNew]
namespace VectorClass
{
[LastModified("2017-7-19", "更新C#7,.NET Core 2")]
[LastModified("2015-6-6", "更新C#6,.NET Core")]
[LastModified("2010-2-14", "修改第一步")]
public class Vector : IFormattable, IEnumerable<double>
{
public Vector(double x, double y, double z)
{
X = x;
Y = y;
Z = z;
}
public Vector(Vector vector) : this(vector.X, vector.Y, vector.Z)
{ }
public double X { get; }
public double Y { get; }
public double Z { get; }
public IEnumerator<double> GetEnumerator()
{
throw new NotImplementedException();
} [LastModified("2017-7-19", "将ijk格式从StringBuilder更改为格式字符串")]
public string ToString(string format, IFormatProvider formatProvider)
{
if (format == null)
{
return ToString();
} switch (format.ToUpper())
{
case "N":
return "|| " + Norm().ToString() + " ||";
case "VE":
return $"( {X:E}, {Y:E}, {Z:E} )";
case "IJK":
return $"{X} i + {Y} j + {Z} k";
default:
return ToString();
}
} public double Norm() => X * X + Y * Y + Z * Z; IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
} [LastModified("2015-6-6", "修改")]
[LastModified("2010-2-14", " 类创建")]
public class VectorEnumerator : IEnumerator<double>
{
public double Current => throw new NotImplementedException(); object IEnumerator.Current => throw new NotImplementedException(); public void Dispose()
{
throw new NotImplementedException();
} public bool MoveNext()
{
throw new NotImplementedException();
} public void Reset()
{
throw new NotImplementedException();
}
}
}
}

  这里我们还需要设置下csproj项目文件添加

<version>2.1.</version>

  到这里我们介绍了自定义特性相关。接下来我们介绍反射,然后根据反射示例加上自定义特性示例去完成一个小的demo。

反射

  反射是.NET中的重要机制,通过反射,可以在运行时获得程序或程序集中每一个类型(包括类、结构、委托、接口和枚举等)的成员和成员的信息。有了反射,即可对每一个类型了如指掌。另外我还可以直接创建对象,即使这个对象的类型在编译时还不知道。

一、System.Type类

Type t=typeof(double);

  这里使用Type类只为了存储类型的引用,以前把Type看做一个类,实际上时一个抽象的基类。实例化一个Type对象,实际上就实例化了Type的一个派生类。尽管一般情况下派生类只提供各种Type方法和属性的不同重载,但是这些方法和属性返回对应数据类型的正确数据。通常,获取指定任何给定类型的Type引用有3中常用的方式:

  • 使用typeof运算符,就想上面的例子一样
  • 使用GetType()方法,所有的类都会从System.Object继承这个方法。
double d = ;

Type t = d.GetType();
  • 调用Type类的静态方法GetType()
 Type t = Type.GetType("System.Double");

  Type是实现许多反射功能的入口,它实现了许多方法和属性,这里我们将介绍如何使用这个类。

属性

返回值

Name

数据类型名称

FullName

数据类型的完全限定名(包括名称空间名)

Namespace

在其中定义数据类型的名称空间名

    其次,属性还可以进一步获取Type对象的引用,这些引用表示相关的类

属性

返回对应的Type引用

BaseType

该Type的直接基本类型

UnderlyingSystemType

该Type在.NET运行库中映射的类型。这个成员只能在完整的框架中使用

  其中还有许多布尔属性表示这种类型是否是一个类。还是一个枚举等等。这些特性包括IsAbstractIsArrayIsClassIsEnumIsInterfaceIsPointerIsPrimitive一种预定义的基元数据类型)、IsPublicIsSealed以及IsValueType例如判断类型是否是数组:

   Type t = typeof(double);

   if (t.IsArray)//返回布尔值

      { 

  }

二、方法

  System.Type的大多数方法都用于获取对应数据类型的成员信息:构造函数、属性、方法和事件等。下面我们看看Type的成员方法,这里遵循一个模式。注意名称为复数形式的方法返回一个数组。

返回的对象类型

方法

ConstructorInfo

GetConstructor(),GetConstructors()

EventInfo

GetEvent(),GetEvents()

FieldInfo

GetField(),GetFields()

MemberInfo

GetMember(),GetMembers(),GetDefaultMembers()

MethodInfo

GetMethod(),GetMethods()

PropertyInfo

GetProperty(),GetProperties()

  GetMember()和GetMembers()方法返回的数据类型的任何成员或所有成员的详细信息,不管这些成员是构造函数、属性、方法等

三、Assembly类

  Assembly类在System.Reflection名称空间定义,它允许访问给定程序集的元数据,它也可以包含可以加载和执行程序集的方法。

  我们可以先看第一个方法Assembly.Load()或者Assembly.LoadFrom()。这两个方法的区别在于Load方法的参数时程序集的名称,运行库会在各个位置搜索该程序集,试图找到该程序集,这些位置包括本地目录和群居程序集缓存。

  1、获取在程序集好难过定义的类型的详细信息

  这里我跟根据Assembly类的一个功能来获取程序集中定义的所有类型的详细信息,只要调用Assembly.GetTypes()方法,他就可以返回一个包含所有类型的详细信息的System.Type引用数组。

 Assembly theAssembly = Assembly.Load(new AssemblyName("VectorClass"));

            Type[] types = theAssembly.GetTypes();

  2、获取自定义特性的详细信息

  用于查找在程序集或类型中定义了什么自定义特性的方法取决于与该特性相关的对象类型。如果要确定程序集从整体上关联了什么自定义特性,就需要调用Assembly类的一个静态方法

  Attribute[] attributes = Attribute.GetCustomAttributes(theAssembly);

完成示例

  到这里我们就简单的介绍了自定义特性以及反射,我们就接着完成我们的示例,刚刚以及定义了两个程序集以及自定义特性。现在我们要做的就是配合反射来获取相关程序集的信息。主要实现效果是:说明公司如何定期升级软件,自动记录升级的信息。

class Program
{
/// <summary>
/// 输出的消息
/// </summary>
private static readonly StringBuilder outputText = new StringBuilder(); /// <summary>
/// 存储的时间
/// </summary>
private static DateTime backDateTo = new DateTime(,,);
static void Main(string[] args)
{
//获取访问的程序集
Assembly theAssembly = Assembly.Load(new AssemblyName("VectorClass"));
//获取自定义特性的详细信息
Attribute supportsAttribute = theAssembly.GetCustomAttribute(typeof(SupportsWhatsNewAttribute));
AddToOutput($"assembly:{theAssembly.FullName}");
if (supportsAttribute==null)
{
AddToOutput("这个程序集不支持");
return;
}
else
{
AddToOutput("定义的类型是:");
}
//获取程序集中定义的公共类型集合
IEnumerable<Type> types = theAssembly.ExportedTypes;
foreach ( Type definedType in types)
{
DisplayTypeInfo(definedType);
}
Console.WriteLine(backDateTo);
Console.WriteLine(outputText.ToString());
Console.ReadLine();
} public static void DisplayTypeInfo(Type type)
{
if (!type.GetTypeInfo().IsClass)
{
return;
}
AddToOutput($"{Environment.NewLine}类 {type.Name}");
//获取类型的详细信息然后获取其自定义详细信息选择自定义特性再筛选时间
IEnumerable<LastModifiedAttribute> lastModifiedAttributes = type.GetTypeInfo().GetCustomAttributes()
.OfType<LastModifiedAttribute>().Where(a => a.DateModified >= backDateTo).ToArray(); if (lastModifiedAttributes.Count()==)
{
AddToOutput($"\t这个{type.Name}没有改变{Environment.NewLine}");
}
else
{
foreach (LastModifiedAttribute item in lastModifiedAttributes)
{
WriteAttributeInfo(item);
}
AddToOutput("这些类的修改方法:"); //获取类的信息中的方法
foreach (MethodInfo methond in type.GetTypeInfo().DeclaredMembers.OfType<MethodInfo>())
{
//获取这些方法的自定义特性信息筛选时间
IEnumerable<LastModifiedAttribute> attributesToMethods = methond.GetCustomAttributes().OfType<LastModifiedAttribute>()
.Where(a => a.DateModified >= backDateTo).ToArray();
if (attributesToMethods.Count()>)
{
AddToOutput($"{methond.ReturnType}{methond.Name}()");
foreach (Attribute attribute in attributesToMethods)
{
WriteAttributeInfo(attribute);
}
}
}
}
} static void AddToOutput(string Text) => outputText.Append("\n" + Text); private static void WriteAttributeInfo(Attribute attribute)
{
if (attribute is LastModifiedAttribute lastModifiedAttribute)
{
AddToOutput($"\tmodified:{lastModifiedAttribute.DateModified:D}:{lastModifiedAttribute.Changes}");
if (lastModifiedAttribute.Issues!=null)
{
AddToOutput($"\tOutstanding issues:{lastModifiedAttribute.Issues}");
}
}
} }

上面都打上了详细备注,完整的项目示例已存放在Github上。有兴趣的可以Download下来看看。

总结

  本篇文章主要介绍了Type和Assembly类,它们是访问反射所提供的扩展功能的主要入口点。反射是.NET中的重要机制,通过反射,可以在运行时获得程序或程序集中每一个类型(包括类、结构、委托、接口和枚举等)的成员和成员的信息。

   不是井里没有水,而是你挖的不够深。不是成功来得慢,而是你努力的不够多。


              c#基础知识详解系列

欢迎大家扫描下方二维码,和我一起学习更多的C#知识 

  

C#之反射、元数据详解的更多相关文章

  1. Java 反射机制详解(下)

    续:Java 反射机制详解(上) 三.怎么使用反射 想要使用反射机制,就必须要先获取到该类的字节码文件对象(.class),通过字节码文件对象,就能够通过该类中的方法获取到我们想要的所有信息(方法,属 ...

  2. Java 反射机制详解(上)

    一.什么是反射 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意方法和属性:这种动态获取信息以及动态调用对象方法的功能称为java ...

  3. Java反射机制详解

    Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反 ...

  4. java反射案例详解

    白首为功名.旧山松竹老,阻归程.欲将心事付瑶琴.知音少,弦断有谁听? [案例1]通过一个对象获得完整的包名和类名 package Reflect; /** * 通过一个对象获得完整的包名和类名 * * ...

  5. Java中反射机制详解

    序言 在学习java基础时,由于学的不扎实,讲的实用性不强,就觉得没用,很多重要的知识就那样一笔带过了,像这个马上要讲的反射机制一样,当时学的时候就忽略了,到后来学习的知识中,很多东西动不动就用反射, ...

  6. [整理]C#反射(Reflection)详解

    本人理解: 装配件:Assembly(程序集) 晚绑定:后期绑定 MSDN:反射(C# 编程指南) -----------------原文如下-------- 1. 什么是反射2. 命名空间与装配件的 ...

  7. (转)C#反射机制详解

    反射的定义:审查元数据并收集关於它的类型信息的能力,元数据(编辑后的基本数据单元)就是一大堆表,编译器会创建一个类定义表,一个字段定义表,一个方法定义表等,System.Reflection命名空间包 ...

  8. C#反射机制详解

    反射的定义:审查元数据并收集关於它的类型信息的能力,元数据(编辑后的基本数据单元)就是一大堆表,编译器会创建一个类定义表,一个字段定义表,一个方法定义表等,System.Reflection命名空间包 ...

  9. 【转载】C#反射机制详解

    反射的定义:审查元数据并收集关於它的类型信息的能力,元数据(编辑后的基本数据单元)就是一大堆表,编译器会创建一个类定义表,一个字段定义表,一个方法定义表等,System.Reflection命名空间包 ...

随机推荐

  1. centos 7 安装docker,conflicts 异常

    [root@localhost html]# yum install docker-io 已加载插件:fastestmirror, langpacks Loading mirror speeds fr ...

  2. 卸载 python 3.7.3 再安装 遇到 error 0x80070001

    这件事告诉我,千万不要手贱,闲的发慌蛋疼 手贱把用得好好的python 3.7.3 卸载后怎么装也装不回去, 告诉我遇到了 error 0x80070001 最终还是靠强大的谷歌找到了办法,幸好没有重 ...

  3. Managing Network Usage

    This lesson describes how to write applications that have fine-grained control over their usage of n ...

  4. 使用WebService发布soap接口,并实现客户端的https验证

    什么是https HTTPS其实是有两部分组成:HTTP + SSL / TLS, 也就是在HTTP上又加了一层处理加密信息的模块,并且会进行身份的验证. 如何进行身份验证? 首先我们要明白什么是对称 ...

  5. kali linux上安装ssh

    1.暂停kali上的ssh进程 root@kali:~# sudo stop ssh 2.卸载ssh服务 root@kali:~# apt-get remove openssh-server 这里可能 ...

  6. python函数知识四 迭代器、生成器

    15.迭代器:工具 1.可迭代对象: ​ 官方声明,只要具有__iter__方法的就是可迭代对象 list,dict,str,set,tuple -- 可迭代对象,使用灵活 #方法一: list.__ ...

  7. 安卓图片加载框架--Universal-Image-Loader

    今天来介绍图片加载的框架Android-Universal-Image-Loader GITHUB上的下载路径为:https://github.com/nostra13/Android-Univers ...

  8. [剑指offer] 3. 从头到尾打印链表

    题目描述 输入一个链表,按链表值从尾到头的顺序返回一个ArrayList. 思路: 利用容器,遍历一遍加入到一个新容器里,然后反置输出. vector 用 reverse stack 则直接一个个出栈 ...

  9. java中的堆、栈、方法区等比较

    • 堆.栈.方法区 1. java中的栈(stack)和堆(heap)是java在内存(ram)中存放数据的地方 2. 堆区 存储的全部是对象,每个对象都包含一个与之对应的class的信息.(clas ...

  10. [leetcode] 120. Triangle (Medium)

    原题 思路: dp,从下往上依次取得最小的,取到最上面的,就是一条最小的路径. class Solution { public: int minimumTotal(vector<vector&l ...