C#反射和特性

参考文章:https://www.cnblogs.com/moonache/p/7532006.html

元数据和反射

什么是反射?

  • 有关程序及其类型的数据被称为元数据(metadata),它们保存在程序的程序集中
  • 程序在运行时,可以查看其他程序集或其本身的元数据。一个运行的程序査看本身的元数据或其他程序的元数据的行为叫做反射(reflection)

那么怎样来反射出数据呢?

BCL声明了一个叫做Type的抽象类,它被设计用来包含类型的特性。使用这个类的对象能让我们获取程序使用的类型的信息。

image

需要了解的有关Type的重要事项如下:

  • 对于程序中用到的每一个类型,CLR都会创建一个包含这个类型信息的Type类型的对象
  • 程序中用到的每一个类型都会关联到独立的Type类的对象
  • 不管创建的类型有多少个实例,只有一个Type对象会关联到所有这些实例

object.GetType()

    /// <summary>
    /// 反射操作(2种:object.GetType() & typeof):一个运行的程序査看本身的元数据或其他程序的元数据的行为叫做反射(reflection)
    /// 有关程序及其类型的数据被称为元数据(metadata),它们保存在程序的程序集中
    /// </summary>
    static void ReflectOp()
    {
        // 使用 object.GetType()
        var bs = new MyBaseClass();
        var ma = new MyClassA();

        MyBaseClass[] bsma = new MyBaseClass[] { bs, ma };

        foreach (var i in bsma)
        {
            Type t = i.GetType();

            Console.WriteLine("对象类型名称:" + t.Name);

            FieldInfo[] ins = t.GetFields();
            foreach (FieldInfo fi in ins)
            {
                Console.WriteLine("     字段名称包含:" + fi.Name);
            }
        }
    }

typeof关键字

        /// <summary>
        /// 反射操作(2种:object.GetType() & typeof):一个运行的程序査看本身的元数据或其他程序的元数据的行为叫做反射(reflection)
        /// 有关程序及其类型的数据被称为元数据(metadata),它们保存在程序的程序集中
        /// </summary>
        static void ReflectOp()
        {
            // 使用typeof关键字
            Type bs = typeof(MyClassA);
            Console.WriteLine("类型名称:" + bs.Name);

            FieldInfo[] bsfs = bs.GetFields();
            foreach (FieldInfo fi in bsfs)
            {
                Console.WriteLine("     字段名称包含:" + fi.Name);
            }
        }

特性

什么是特性?

特性(attribute)是一种允许我们向程序的程序集增加元数据的语言结构。它是用于保存程序结构信息的某种特殊类型的类。

这么看不太理解,先让我们看看特性是怎么使用的吧。

特性的使用

预定义的特性

#define DoRun
...// 其他代码

        /// <summary>
        /// 使用Obsolete废弃特性
        /// 警告他们不要使用旧方法(有新方法代替),可以使用Obsolete特性将程序结构标注为过期的,并且在代码编译时显式有用的警告消息。
        /// </summary>
        [Obsolete("方法已弃用")]
        static void PintOut()
        {
            Console.WriteLine("这是一个打印方法。");
        }

        /// <summary>
        /// 使用Conditional特性(相当于C的条件编译)
        /// Conditional特性允许我们包括或排斥特定方法的所有调用。为方法声明应用Conditional特性并把编译符作为参数来使用。
        ///     如果定义了编译符号,那么编译器会包含所有调用这个方法的代码,这和普通方法没有什么区别
        ///     如果没有定义编译符号,那么编译器会忽略代码中这个方法的所有调用
        /// </summary>
        /// <param name="message"></param>
        [Conditional("DoRun")]// 这里在第一行定义了 DoRun
        static void RunMessage(string message)
        {
            Console.WriteLine(message);
        }

        /// <summary>
        /// 调用者信息特性可以访问文件路径、代码行数、调用成员的名称等源代码信息。
        ///     - 这三个特性名称为CallerFilePath、CallerLineNumber和CallerMemberName
        ///     - 这些特性只能用于方法中的可选参数:如果调用方法时显式指定了这些参数,则会使用真正的参数值。
        ///         + 但在此方法中调用时,没有显式提供这些值,
        ///         + 因此系统将会提供源代码的文件路径、调用该方法的代码行数和调用该方法的成员名称
        /// </summary>
        /// <param name="message"></param>
        /// <param name="fileName"></param>
        /// <param name="lineNumber"></param>
        /// <param name="callingMember"></param>
        static void MyTrace(string message,
                               [CallerFilePath] string fileName = "",
                               [CallerLineNumber] int lineNumber = 0,
                               [CallerMemberName] string callingMember = "")
        {
            Console.WriteLine("File:         {0}", fileName);
            Console.WriteLine("Line:         {0}", lineNumber);
            Console.WriteLine("Called From:  {0}", callingMember);
            Console.WriteLine("Message:      {0}", message);
        }

        /// <summary>
        /// 测试DebuggerStepThrough特性方法
        /// </summary>
        static void TestDebuggerStepThroughAttribute()
        {
            var p = new MyDebuggerStepThroughAttributeTest();
            p.IncrementFields();
            p.X = 5;
            Console.WriteLine("测试MyDebuggerStepThroughAttributeTest特性:
"
                + "     X = {0}, Y = {1}", p.X, p.Y);
        }

其他预定义特性如下:

image

自定义特性

有关特性类的一些要点如下:

  • 声明一个派生自System.Attribute的类
  • 给它起一个以后缀Attribute结尾的名字
  • 由于特性持有目标的信息,所有特性类的公共成员只能是:字段、属性、构造函数

那么我们该如何访问特性呢?

使用IsDefined方法

我们可以使用Type对象的IsDefined方法来检测某个特性是否应用到了某个类上。

[AttributeUsage(AttributeTargets.Class)]
public sealed class ReviewCommentAttribute:System.Attribute
{…}

[ReviewComment("Check it out","2.4")]
class MyClass{}

class Program
{
    static void Main()
    {
        var mc=new MyClass();
        Type t=mc.GetType();
        bool isDefined=
            t.IsDefined(typeof(ReviewCommentAttribute),false);
        if(isDefined)
            Console.WriteLine("ReviewComment is applied to type {0}",t.Name);
    }
}

image

使用GetCustomAttributes方法

GetCustomAttributes方法返回应用到结构的特性的数组。

  • 实际返冋的对象是object的数组,因此我们必须将它强制转换为相应的特性类型
  • 布尔参数指定是否搜索继承树来査找特性
  • object[] AttArr = t.GetCustomAttributes(false);
  • 调用GetCustomAttributes方法后,每一个与目标相关联的特性的实例就会被创建
using System;

[AttributeUsage(AttributeTargets.Class)]
public sealed class MyAttributeAttribute:System.Attribute
{
    public  string  Description  {get;set;}
    public  string  VersionNumber{get;set;}
    public  string  ReviewerID   {get;set;}

    public MyAttributeAttribute(string desc,string ver)
    {
        Description=desc;
        VersionNumber=ver;
    }
}

[MyAttribute("Check it out","2.4")]
class MyClass
{
}
class Program
{
    static void Main()
    {
        Type t=typeof(MyClass);
        object[] AttArr=t.GetCustomAttributes(false);

        foreach(Attribute a in AttArr)
        {
            var attr=a as MyAttributeAttribute;
            if(null!=attr)
            {
                Console.WriteLine("Description    :{0}",attr.Description);
                Console.WriteLine("Version Number :{0}",attr.VersionNumber);
                Console.WriteLine("Reviewer ID    :{0}",attr.ReviewerID);
            }
        }
    }
}

image

原文地址:https://www.cnblogs.com/swbna/p/17454669.html