(十二)反射与特性 -反射与预定义特性(1)

一、反射

1、什么是反射

  • 了解反射之前,要先了解一下元数据。元数据指保存在程序集中的一些有关程序及其类型的数据,包括类、结构、委托、接口和枚举等)的成员和成员的信息。

  • 程序在运行时,可以查看程序集以及其本身的元数据,是反射。

  • 通过反射,可以在运行时获取程序或程序集中的所有类型的元数据。

  • 反射指程序可以访问、检测和修改它本身状态或行为的一种能力。(来源:菜鸟教程)

  • 反射的命名空间是System.Reflection。

具体内容:

.NET 中的反射

2、反射的优缺点

首先在编译中分为动态编译和静态编译,静态编译是在编译中确定类型,绑定对象,而动态编译是在运行中确定类型,绑定对象
反射的优点就是可以动态创建对象、绑定对象,提高了程序的灵活性和扩展性,但反射是一种解释操作,在性能上不如静态编译快

3、 反射(Reflection)的用途

反射(Reflection)有下列用途:

  • 它允许在运行时查看特性(attribute)信息。
  • 它允许审查集合中的各种类型,以及实例化这些类型。
  • 它允许延迟绑定的方法和属性(property)。
  • 它允许在运行时创建新类型,然后使用这些类型执行一些任务。

4、Type 类

通过反射类的Type,来获取有关构造函数、方法、字段、属性和事件的信息。

Type 是抽象类。在运行时,CLR 创建从 Type(RuntimeType)派生的类的实例,Type 包含了类型信息。当访问这些实例时,CLR 不会返回派生类的引用而是返回 Type 基类的引用

如声明了一个 MyClass 类型,不管创建了多少个 MyClass 类型的实例,就只有一个 Type 对象表示它。即每一个类型对应一个 Type 类型的对象。

表-System.Type 类的部分成员

成员 成员类型 描述
Name 属性 返回类型的名字
Namespace 属性 返回包含类型声明的命名空间
Assembly 属性 返回声明类型的程序集。如果类型是泛型的,返回定义这个类型的程序集
GetFields 方法 返回类型的字段列表
GetProperties 方法 返回类型的属性列表
GetMethods 方法 返回类型的方法列表

1)获取 Type 对象

通过 object 对象 来获取 Type 对象。

Type t = myInstance.GetType():

示例:

class BaseClass
    {
        public int BaseField = 0;
    }

    class DerivedClass: BaseClass
    {
        public int DerivedField = 0;
    }

    class Program
    {
        static void Main(string[] args)
        {
            var bc = new BaseClass();
            var dc = new DerivedClass();

            BaseClass[] bca = new BaseClass[] { bc, dc };

            foreach(var v in bca)
            {
                Type t = v.GetType();
                Console.WriteLine($"Object type :{ t.Name }");

                FieldInfo[] fi = t.GetFields();
                foreach (var f in fi)
                    Console.WriteLine($"     Field :{ f.Name }");

                Console.WriteLine();
            }

            //Arry 枚举器的Type 类型
            int[] arr = { 10, 11, 12, 13 };
            var arrEnumerator = arr.GetEnumerator();
            Type ArrEnumeratorType = arrEnumerator.GetType();
           var arrEnumeratorProperties = ArrEnumeratorType.GetProperties();

            Console.WriteLine($"Object type :{ ArrEnumeratorType.Name }");

            foreach (var item in arrEnumeratorProperties)
                Console.WriteLine($"   Properties:{ item.Name }");

            Console.ReadKey();
        }
    }

输出结果:

Object type :BaseClass
     Field :BaseField

Object type :DerivedClass
     Field :DerivedField
     Field :BaseField

Object type :SZArrayEnumerator
   Properties:Current

2)使用 typeof 运算符来获取 Type 对象。

    class BaseClass
    {
        public int BaseField = 0;
    }

    class DerivedClass: BaseClass
    {
        public int DerivedField = 0;
    }

    class Program
    {
        static void Main(string[] args)
        {
            Type tbc = typeof(DerivedClass);
            Console.WriteLine($"Object type :{ tbc.Name}");

            FieldInfo[] fi = tbc.GetFields();
            foreach (var f in fi)
                Console.WriteLine($"      Field : { f.Name }");

            Console.ReadKey();
        }
    }

输出结果:

Object type :DerivedClass
Field : DerivedField
Field : BaseField

二、特性

1、什么是特性

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

  • 将应用特性的程序结构叫作目标。
  • 设计用来获取和使用元数据的程序(比如对象浏览器)叫作特性的消费者。
  • .NET 预定了很多特性,我们也可以声明自定义特性。

关于图中的特性:

  • 在源码中将特性应用于程序结构。
  • 编译器获取源代码并且从特性产生元数据,然后把元数据放到程序集中。
  • 消费者程序可以获取特性的元数据以及程序中其他组件的元数据,注意,编译器同时产生和消费特性。

(十二)反射与特性 -反射与预定义特性(1)_第1张图片

2、应用特性

特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集。

[Serializable]
public class MyClass
{...}

[MyAttribute("Simple class","Version 3.57")]//带有参数的特性
public class MyOtherClass
{...}

3、预定义的保留特性

1)Obsolete 特性

可以使用 Obsolete 特性将程序结构标注为“过时”,以显示有用的警告消息。

class Program
{
[Obsolete("Use method SuperPrintOut")]//将特性应用到方法
static void PrintOut(string str)
{
Console.WriteLine(str);
}
static void Main(string[] args)
{
PrintOut("Start of Main");//调用 Obsolete 方法
}
}

在 VS 底部栏,错误列表里,显示警告信息:

严重性 代码 说明 项目 文件 行
警告 CS0618 “Program.PrintOut(string)”已过时:“Use method SuperPrintOut”…

如将 Obsolete 特性改为:

[Obsolete("Use method SuperPrintOut",true)]

在 VS 底部栏,错误列表里,显示错误信息:

严重性 代码 说明 项目 文件 行
错误 CS0619 “Program.PrintOut(string)”已过时:“Use method SuperPrintOut”…

2)Conditional 特性

Conditional 特性允许我们包括或排斥特定方法的所有调用。

在方法上使用 Conditional 特性的规则:

  • 该方法必须是类型或结构体的方法。
  • 该方法必须为 void 类型。
  • 该方法不能被声明 overrride,但可以标记为 virtual。
  • 该方法不能是接口方法的实现。
#define DoTrace
using System;
//....
    class Program
    {
        [Conditional("DoTrace")]
        static void TraceMessage(string str)
        {
            Console.WriteLine(str);
        }

        static void Main(string[] args)
        {
            TraceMessage("Start of Main");
            Console.WriteLine("Doing work in Main");
            TraceMessage("End of Main");
            Console.ReadKey();
        }
    }

若定义了编译符号#define DoTrace,输出结果:

Start of Main
Doing work in Main
End of Main

若没有定义编译符号#define DoTrace,输出结果:

Doing work in Main

3)调用者信息特性

利用调用者信息特性可以访问文件路径、代码行数、调用成员的名称等源代码信息。

  • 这3个特性名称为 CallerFilePath、CallerLineNumber 和 CallerMemberName。
  • 这些特性只能用于方法中的可选参数。
    class Program
    {
       public static void MyTrace(string message,
           [CallerFilePath] string fileName = "",
           [CallerLineNumber] int lineNumber = 0,
           [CallerMemberName] string callingMember=""
           )
        {
            Console.WriteLine($"File:   { fileName }");
            Console.WriteLine($"Line:   { lineNumber }");
            Console.WriteLine($"Called From:   { callingMember }");
            Console.WriteLine($"Message:   { message }");
        }

        static void Main(string[] args)
        {
            MyTrace("Simple message");
            Console.ReadKey();
        }
    }

输出结果:

File: C:\Users\用户名\documents\visual studio 2015\Projects\ConsoleApplication2\ConsoleApplication2\Program.cs
Line: 35
Called From: Main
Message: Simple message

4)DebuggerStepThrough 特性

在单步调试代码时,可以通过 DebuggerStepThrough 特性不让调试器进入某些方法里进行调试,而是执行该方法后,直接继续调试下一行代码。

DebuggerStepThrough 特性:

  • 该特性位于 System.Diagnostics 命名空间。
  • 该特性可用于类、结构、构造函数、方法或访问器。
    class Program
    {
        int x = 1;
        int X
        {
            get { return x; }
            [DebuggerStepThrough] //不进入 set 访问器
            set
            {
                x = x * 2;
                x += value;
            }
        }

        public int Y { get; set; }

        [DebuggerStepThrough]//不进入这个方法
        void IncrementFields()
        {
            X++;Y++;
        }

        static void Main(string[] args)
        {
            Program p = new Program();
            p.IncrementFields();
            p.X = 5;
            Console.WriteLine($"X = { p.X }, Y = { p.Y }");
            Console.ReadKey();
        }
    }

调试过程:

(十二)反射与特性 -反射与预定义特性(1)_第2张图片

4、其他预定义特性

特性 意义
CLSCompliant 声明公开暴露的成员应该被编译器检测其是否符合 CLS。兼容的程序集可以被任何兼容 .NET 的语言使用
Serializable 声明结构可以被序列化
NonSerialized 声明结构不能被序列化
DLLImport 声明是非托管代码实现的
WebMethod 声明方法应该被作为 XML Web 服务的一部分暴露
AttributeUsage 声明特性能应用于什么类型的程序结构。将这个特性应用到特性声明上

5、多个特性

//多层结构
[Serializable]
[MyAtrribute("Simple class","Version 3.57")]

//逗号分隔
[MyAtrribute("Simple class","Version 3.57"),Serializable]

6、其他类型的目标

目标:字段和属性等程序结构

//字段上的特性
[MyAtrribute("Holds a value","Version 3.2")]
public int MyField;

//方法上的特性
[Obsolete]
[MyAtrribute("Prints out a message.","Version 3.6")]
public void PrintOut()
{
...
}

显示地标注特性:

[method: MyAtrribute("Prints out a message.","Version 3.6")]
[return: MyAtrribute("This value represents...","Version 2.3")]
public long ReturnSetting()
{
...
}

表-特性目标:
其中 type 覆盖了类、结构、委托、枚举和接口; typevar 目标名称为使用泛型的结构指定类型参数。

特性目标
event field
method param
property return
type typevar
assembly module

7、全局特性

通过使用 assembly 和 module 目标名称来使用显式目标说明符把特性设置在程序集或模块级别。

  • 程序集级别的特性必须放置在任何命名空间之外,并且通常放置在 AssemblyInfo.cs 文件中。
  • AssemblyInfo.cs 文件通常包含有关公司、产品以及版权信息的元数据。

AssemblyInfo.cs 文件中代码段:

[assembly: AssemblyTitle("SuperWidget")]
[assembly: AssemblyDescription("Implements the SuperWidget product.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("McArthur Widgets,Inc.")]
[assembly: AssemblyProduct("Super Widget Deluxe")]
[assembly: AssemblyCopyright("Copyright McArthur Widgets 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

你可能感兴趣的:(CSharp,c#)