22.特性

22.反射和特性-特性


22.1 知识点

特性是什么

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

特性提供功能强大的方法以将声明信息与 C# 代码(类型、方法、属性等)相关联。特性与程序实体关联后,即可在运行时使用反射查询特性信息。

特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集中。它可以放置在几乎所有的声明中(类、变量、函数等等声明)。

说人话:

特性本质是个类,我们可以利用特性类为元数据添加额外信息。比如一个类、成员变量、成员方法等等为他们添加更多的额外信息。之后可以通过反射来获取这些额外信息。

自定义特性

  • 特性基类 Attribute
  • 自定义特性 继承 特性基类Attribute 来实现自定义特性
  • 一般来说,自定义特性的命名是 自定义特性名 + Attribute 后缀
// 实例
// 我的自定义特性类
class MyCustomAttribute : Attribute
{
    // 特性中的成员,一般根据需求来写
    public string myCustomAttributeInfo;

    public MyCustomAttribute(string myCustomAttributeInfo)
    {
        this.myCustomAttributeInfo = myCustomAttributeInfo;
    }

    public void MyCustomAttributeFun()
    {
        Console.WriteLine("我的自定义特性类中的函数");
    }
}

特性的使用

  • 基本语法: [特性名(参数列表)](注意:一般不包括 Attribute 后缀,系统默认省略)
  • 本质上,就是在调用特性类的构造函数,参数列表是构造函数要传的参数
  • 写在哪里?
    • 类、函数、变量上一行,表示他们具有该特性信息
// 实例
[MyCustom("这个是我自己写的一个我的类")]
class MyClass
{
    [MyCustom("这是我的类中的一个成员变量")]
    public int myClassValue;

    [MyCustom("这是我的类中的一个用于测试的函数")]
    public void myClassFun([MyCustom("这是我的类中的测试函数的参数")] int a)
    {

    }
}

在主函数中使用

实例化测试类对象

//创建myClass变量 得到myClass的Type
MyClass myClass = new MyClass();
Type myClassType = myClass.GetType();
//myClassType = typeof(MyClass);
//myClassType = Type.GetType("Lesson21_特性.MyClass");

Type.IsDefined方法 判断这个类是否使用了某个特性

//Type类中的IsDefined方法 判断这个类是否使用了某个特性 返回一个bool值
//参数一:特性的类型 传入特性的Type
//参数二:代表是否搜索继承链 比如父类里面有没有这个特性 属性和事件忽略此参数
//注意:Type类中的IsDefined方法只会判断这个类有没有使用某个特性 假如这个类没有使用这个特性 但是类中的其他成员使用了这个特性 仍然返回false
if (myClassType.IsDefined(typeof(MyCustomAttribute), false))
{
    Console.WriteLine("该类型应用了MyCustom特性");//该类型应用了MyCustom特性
}

Type.GetCustomAttributes方法 获取Type类元数据中的所有特性 一个类可能有很多特性一起修饰 返回一个object数组

//Type类中的GetCustomAttributes方法 获取Type类元数据中的所有特性 一个类可能有很多特性一起修饰 返回一个object数组
//参数一:代表是否搜索继承链 比如父类里面有没有这个特性 属性和事件忽略此参数
object[] myClassTypeAllCustomAttributesArray = myClassType.GetCustomAttributes(true);

//for循环遍历Type类元数据中的所有特性数组 判断特性是不是你想得到的特性 是的话可以调用特性中的成员
for (int i = 0; i < myClassTypeAllCustomAttributesArray.Length; i++)
{
    //一个类可能有很多特性一起修饰 用is判断特性是不是你想得到的特性
    if (myClassTypeAllCustomAttributesArray[i] is MyCustomAttribute)
    {
        //输出我的自定义特性类中的信息
        Console.WriteLine((myClassTypeAllCustomAttributesArray[i] as MyCustomAttribute).myCustomAttributeInfo);//这个是我自己写的一个我的类
        //调用我的自定义特性类中的函数
        (myClassTypeAllCustomAttributesArray[i] as MyCustomAttribute).MyCustomAttributeFun();//我的自定义特性类中的函数
    }
}

限制自定义特性的使用范围

  • 通过为特性类加特性限制自定义特性的使用范围
  • 基本语法:[AttributeUsage(AttributeTargets.XX | AttributeTargets.XX, AllowMultiple = ?, Inherited = ?)]
    • 参数一:AttributeTargets — 特性能够用在哪些地方,| 代表或
    • 参数二:AllowMultiple — 是否允许多个特性实例用在同一个目标上,默认是 false
    • 参数三:Inherited — 特性是否能被派生类和重写成员继承,默认是 false
//实例

//我的自定义特性类2 只能使用到 类 结构体 上 允许多个特性实例用在同一个目标上 特性能被派生类和重写成员继承
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true, Inherited = true)]
public class MyCustom2Attribute : Attribute
{

}

//我的自定义特性类3 只能使用到 类 变量 上 允许多个特性实例用在同一个目标上 特性能被派生类和重写成员继承
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, AllowMultiple = true, Inherited = false)]
public class MyCustom3Attribute : Attribute
{

}


[MyCustom2()]//MyCustom2能使用到 类 结构体 上
[MyCustom2()]//MyCustom2允许多个特性实例用在同一个目标上
[MyCustom3()]//MyCustom3能使用到 类 变量 上
[MyCustom3()]//MyCustom3允许多个特性实例用在同一个目标上
//我的类2
class MyClass2
{
    //[MyCustom2()]//MyCustom2报错 只能使用到 类 结构体 上
    [MyCustom3()]//MyCustom3能使用到 类 变量 上
    public int myClass2Value;

    //[MyCustom2()]//MyCustom2报错 只能使用到 类 结构体 上
    //[MyCustom3()]//MyCustom3报错 只能使用到 类 变量 上
    public void myClass2Fun(int a)
    //public void myClass2Fun([MyCustom()] int a)//MyCustom2报错 只能使用到 类 结构体 上
    {

    }
}

系统自带特性——过时特性

  • 用于提示用户使用的方法等成员已经过时,建议使用新方法
  • 一般加在函数前的特性
  • 关键字:Obsolete
  • 基本语法:[Obsolete("提示的内容", true或false)]
    • 参数一:调用过时方法时提示的内容
    • 参数二:true - 使用该方法时会报错,false - 使用该方法时直接警告
//实例

//测试说话类
class TestSpeakClass
{
    [Obsolete("OldSpeak方法已经过时了,请使用NewSpeak方法", false)]
    //老说话方法
    public void OldSpeak(string info)
    {
        Console.WriteLine(info);
    }

    //新说话方法
    public void NewSpeak(string info)
    {
        Console.WriteLine(info);
    }
}

在主函数内。

//创建测试说话类实例
TestSpeakClass testSpeakClass = new TestSpeakClass();
testSpeakClass.OldSpeak("老说话方法");//老说话方法
//因为Obsolete构造时 第二个参数是flase 调用时会发出警告:OldSpeak方法已经过时了,请使用NewSpeak方法
//假如Obsolete构造时 第二个参数是true 会直接报错
testSpeakClass.NewSpeak("新说话方法");//新说话方法

系统自带特性——调用者信息特性

  • 哪个文件调用?
    • CallerFilePath特性:[CallerFilePath]
  • 哪一行调用?
    • CallerLineNumber特性:[CallerLineNumber]
  • 哪个函数调用?
    • CallerMemberName特性:[CallerMemberName]

需要引用命名空间 using System.Runtime.CompilerServices;

一般作为函数参数的特性,有时会在try catch中的catch中打印调用者信息特性。
调用者信息特性作为函数参数的特性时,必须要有默认值。
这样调用函数时,假如有调用者信息特性作为函数参数的参数不传值的话,他就能把调用者信息替换到默认值上,可以写逻辑在函数中打印出来。

在测试说话类中添加说出调用者信息方法。

//说出调用者信息方法
public void SpeakCallerInfo(string info, [CallerFilePath] string fileName = "",
    [CallerLineNumber] int line = 0, [CallerMemberName] string target = "")
{
    Console.WriteLine(info);
    Console.WriteLine(fileName);
    Console.WriteLine(line);
    Console.WriteLine(target);
}

在主函数内。

//说出调用者信息方法 试着只传info 不传后面的调用者信息
//public void SpeakCallerInfo(string info, [CallerFilePath] string fileName = "",
//    [CallerLineNumber] int line = 0, [CallerMemberName] string target = "")
testSpeakClass.SpeakCallerInfo("SpeakCallerInfo方法");
//E:\GameDevelopment\GameDevelopmentLearning\C#\C#进阶\CSharp进阶教程\Lesson21_特性\Program.cs
//275
//Main

系统自带特性——条件编译特性

  • 条件编译特性
    • 关键字:Conditional
    • 基本语法:[Conditional("#define定义的符号")]
    • 它会和预处理指令 #define 配合使用
    • 需要引用命名空间 using System.Diagnostics;
    • 主要可以用在一些调试代码上
    • 有时想执行有时不想执行的代码
    • 假如没有定义传入的 define 定义的符号就不会执行
    • 假如有定义传入的 define 定义的符号就会执行
//实例
[Conditional("ConditionalCompilationFun")]
//条件编译方法
static void ConditionalCompilationFun()
{
    Console.WriteLine("条件编译方法执行");
}

在脚本最前面。

// 定义 ConditionalCompilationFun
#define ConditionalCompilationFun

在主函数内。

// 因为脚本最前面定义 ConditionalCompilationFun,所以能正常执行,否则执行无效
ConditionalCompilationFun(); // 条件编译方法执行

系统自带特性——外部 DLL 包函数特性

  • 外部Dll包函数特性
    • 关键字:DllImport

    • 基本语法:[DllImport("外部Dll包的路径或文件名")]

    • 用来标记非.Net(C#)的函数,表明该函数在一个外部的DLL中定义。

    • 一般用来调用 C 或者 C++ 的Dll包写好的方法。

    • 需要引用命名空间 using System.Runtime.InteropServices

// 实例
// 假设某个 C++ 的 DLL 包和当前程序集放在同一目录 
[DllImport("CPP.dll")]
// 映射 C++ 的 DLL 包的加法方法到 C# 里来,C++ 的 DLL 包你会有相同函数名、参数、返回值的函数,这样调用的时候就是调用 C++ 的 DLL 包的加法方法的逻辑
public static extern int Add(int a, int b);

总结

  • 特性是用于为元数据再添加更多的额外信息(变量、方法等等)。
  • 我们可以通过反射获取这些额外的数据来进行一些特殊的处理。
  • 自定义特性——继承 Attribute 类。
  • 系统自带特性:过时特性。
  • 为什么要学习特性:Unity 引擎中很多地方都用到了特性来进行一些特殊处理。

22.2 知识点代码

//脚本最前面

#region 知识点七 系统自带特性——条件编译特性

//定义ConditionalCompilationFun
#define ConditionalCompilationFun

#endregion

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace Lesson21_特性
{
    #region 知识点一 特性是什么
    //特性是一种允许我们向程序的程序集添加元数据的语言结构
    //它是用于保存程序结构信息的某种特殊类型的类

    //特性提供功能强大的方法以将声明信息与 C# 代码(类型、方法、属性等)相关联。
    //特性与程序实体关联后,即可在运行时使用反射查询特性信息

    //特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集中
    //它可以放置在几乎所有的声明中(类、变量、函数等等申明)

    //说人话:
    //特性本质是个类
    //我们可以利用特性类为元数据添加额外信息
    //比如一个类、成员变量、成员方法等等为他们添加更多的额外信息
    //之后可以通过反射来获取这些额外信息
    #endregion

    //class语句块外 namespace语句块内

    #region 知识点二 自定义特性
    //特性基类 Attribute
    //继承特性基类Attribute来实现自定义特性
    //一般来说 自定义特性的命名是 自定义特性名 + Attribute后缀

    //实例
    //我的自定义特性类
    class MyCustomAttribute : Attribute
    {
        //特性中的成员 一般根据需求来写
        public string myCustomAttributeInfo;

        public MyCustomAttribute(string myCustomAttributeInfo)
        {
            this.myCustomAttributeInfo = myCustomAttributeInfo;
        }

        public void MyCustomAttributeFun()
        {
            Console.WriteLine("我的自定义特性类中的函数");
        }
    }
    #endregion

    #region 知识点三 特性的使用

    //基本语法:[特性名(参数列表)](注意:一般不包括Attribute后缀,系统默认省略)
    //本质上 就是在调用特性类的构造函数 参数列表是构造函数要传的参数
    //写在哪里?
    //类、函数、变量上一行,表示他们具有该特性信息

    //实例


    [MyCustom("这个是我自己写的一个我的类")]
    //[MyCustom("这个是我自己写的一个我的类")]
    //我的类
    class MyClass
    {
        [MyCustom("这是我的类中的一个成员变量")]
        public int myClassValue;

        [MyCustom("这是我的类中的一个用于测试的函数")]
        public void myClassFun([MyCustom("这是我的类中的测试函数的参数")] int a)
        {

        }


        //public void MyCustomAttributeFun(int a)
        //{

        //}
    }


    #endregion

    #region 知识点四 限制自定义特性的使用范围

    //通过为特性类 加特性 限制自定义特性的使用范围
    //基本语法:[AttributeUsage(AttributeTargets.XX | AttributeTargets.XX, AllowMultiple = ?, Inherited = ?)]
    //参数一:AttributeTargets —— 特性能够用在哪些地方 |代表或
    //参数二:AllowMultiple —— 是否允许多个特性实例用在同一个目标上 默认是false
    //参数三:Inherited —— 特性是否能被派生类和重写成员继承 默认是false

    //实例

    //我的自定义特性类2 只能使用到 类 结构体 上 允许多个特性实例用在同一个目标上 特性能被派生类和重写成员继承
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true, Inherited = true)]
    public class MyCustom2Attribute : Attribute
    {

    }

    //我的自定义特性类3 只能使用到 类 变量 上 允许多个特性实例用在同一个目标上 特性能被派生类和重写成员继承
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, AllowMultiple = true, Inherited = false)]
    public class MyCustom3Attribute : Attribute
    {

    }


    [MyCustom2()]//MyCustom2能使用到 类 结构体 上
    [MyCustom2()]//MyCustom2允许多个特性实例用在同一个目标上
    [MyCustom3()]//MyCustom3能使用到 类 变量 上
    [MyCustom3()]//MyCustom3允许多个特性实例用在同一个目标上
    //我的类2
    class MyClass2
    {
        //[MyCustom2()]//MyCustom2报错 只能使用到 类 结构体 上
        [MyCustom3()]//MyCustom3能使用到 类 变量 上
        public int myClass2Value;

        //[MyCustom2()]//MyCustom2报错 只能使用到 类 结构体 上
        //[MyCustom3()]//MyCustom3报错 只能使用到 类 变量 上
        public void myClass2Fun(int a)
        //public void myClass2Fun([MyCustom()] int a)//MyCustom2报错 只能使用到 类 结构体 上
        {

        }
    }

    #endregion

    #region 知识点五 系统自带特性——过时特性

    //过时特性
    //用于提示用户 使用的方法等成员已经过时 建议使用新方法
    //一般加在函数前的特性
    //关键字:Obsolete
    //基本语法:[Obsolete("提示的内容", true或false)]
    //参数一:调用过时方法时 提示的内容
    //参数二:true-使用该方法时会报错  false-使用该方法时直接警告

    //实例

    //测试说话类
    class TestSpeakClass
    {
        [Obsolete("OldSpeak方法已经过时了,请使用NewSpeak方法", false)]
        //老说话方法
        public void OldSpeak(string info)
        {
            Console.WriteLine(info);
        }

        //新说话方法
        public void NewSpeak(string info)
        {
            Console.WriteLine(info);
        }


        //测试说话类中

        #region 知识点六 系统自带特性——调用者信息特性


        //哪个文件调用?
        //CallerFilePath特性:[CallerFilePath]
        //哪一行调用?
        //CallerLineNumber特性:[CallerLineNumber]
        //哪个函数调用?
        //CallerMemberName特性:[CallerMemberName]

        //需要引用命名空间 using System.Runtime.CompilerServices;
        //一般作为函数参数的特性 有时会在try catch中的catch中打印调用者信息特性
        //调用者信息特性作为函数参数的特性时 必须要有默认值
        //这样调用函数时 假如有调用者信息特性作为函数参数的参数不传值的话 他就能把调用者信息替换到默认值上 可以写逻辑在函数中打印出来

        //说出调用者信息方法
        public void SpeakCallerInfo(string info, [CallerFilePath] string fileName = "",
            [CallerLineNumber] int line = 0, [CallerMemberName] string target = "")
        {
            Console.WriteLine(info);
            Console.WriteLine(fileName);
            Console.WriteLine(line);
            Console.WriteLine(target);
        }

        #endregion
    }

    #endregion


    class Program
    {
        //class语句块内 主函数外


        #region 知识点七 系统自带特性——条件编译特性

        //条件编译特性
        //关键字:Conditional
        //基本语法:[Conditional("#define定义的符号")]
        //它会和预处理指令 #define配合使用
        //需要引用命名空间using System.Diagnostics;
        //主要可以用在一些调试代码上
        //有时想执行有时不想执行的代码
        //假如没有定义传入的define定义的符号 就不会执行
        //假如有定义传入的define定义的符号 就会执行

        //实例
        [Conditional("ConditionalCompilationFun")]
        //条件编译方法
        static void ConditionalCompilationFun()
        {
            Console.WriteLine("条件编译方法执行");
        }

        #endregion

        #region 知识点八 系统自带特性——外部Dll包函数特性

        //外部Dll包函数特性
        //关键字:DllImport
        //基本语法:[DllImport("外部Dll包的路径或文件名")]

        //用来标记非.Net(C#)的函数,表明该函数在一个外部的DLL中定义。
        //一般用来调用 C或者C++的Dll包写好的方法
        //需要引用命名空间 using System.Runtime.InteropServices


        //实例
        //假设某个C++的Dll包和当前程序集放在同一目录 
        [DllImport("CPP.dll")]
        //映射C++的Dll包的加法方法到C#里来 C++的Dll包你会有 相同 函数名 参数 返回值 的函数 这样调用的时候就是调用C++的Dll包的加法方法的逻辑
        public static extern int Add(int a, int b);


        #endregion




        static void Main(string[] args)
        {
            Console.WriteLine("特性");

            //主函数内

            #region 知识点三 特性的使用

            //创建myClass变量 得到myClass的Type
            MyClass myClass = new MyClass();
            Type myClassType = myClass.GetType();
            //myClassType = typeof(MyClass);
            //myClassType = Type.GetType("Lesson21_特性.MyClass");

            //Type类中的IsDefined方法 判断这个类是否使用了某个特性 返回一个bool值
            //参数一:特性的类型 传入特性的Type
            //参数二:代表是否搜索继承链 比如父类里面有没有这个特性 属性和事件忽略此参数
            //注意:Type类中的IsDefined方法只会判断这个类有没有使用某个特性 假如这个类没有使用这个特性 但是类中的其他成员使用了这个特性 仍然返回false
            if (myClassType.IsDefined(typeof(MyCustomAttribute), false))
            {
                Console.WriteLine("该类型应用了MyCustom特性");//该类型应用了MyCustom特性
            }

            //Type类中的GetCustomAttributes方法 获取Type类元数据中的所有特性 一个类可能有很多特性一起修饰 返回一个object数组
            //参数一:代表是否搜索继承链 比如父类里面有没有这个特性 属性和事件忽略此参数
            object[] myClassTypeAllCustomAttributesArray = myClassType.GetCustomAttributes(true);

            //for循环遍历Type类元数据中的所有特性数组 判断特性是不是你想得到的特性 是的话可以调用特性中的成员
            for (int i = 0; i < myClassTypeAllCustomAttributesArray.Length; i++)
            {
                //一个类可能有很多特性一起修饰 用is判断特性是不是你想得到的特性
                if (myClassTypeAllCustomAttributesArray[i] is MyCustomAttribute)
                {
                    //输出我的自定义特性类中的信息
                    Console.WriteLine((myClassTypeAllCustomAttributesArray[i] as MyCustomAttribute).myCustomAttributeInfo);//这个是我自己写的一个我的类
                    //调用我的自定义特性类中的函数
                    (myClassTypeAllCustomAttributesArray[i] as MyCustomAttribute).MyCustomAttributeFun();//我的自定义特性类中的函数
                }
            }

            #endregion

            #region 知识点五 系统自带特性——过时特性

            //创建测试说话类实例
            TestSpeakClass testSpeakClass = new TestSpeakClass();
            testSpeakClass.OldSpeak("老说话方法");//老说话方法
            //因为Obsolete构造时 第二个参数是flase 调用时会发出警告:OldSpeak方法已经过时了,请使用NewSpeak方法
            //假如Obsolete构造时 第二个参数是true 会直接报错
            testSpeakClass.NewSpeak("新说话方法");//新说话方法

            #endregion

            #region 知识点六 系统自带特性——调用者信息特性

            //说出调用者信息方法 试着只传info 不传后面的调用者信息
            //public void SpeakCallerInfo(string info, [CallerFilePath] string fileName = "",
            //    [CallerLineNumber] int line = 0, [CallerMemberName] string target = "")
            testSpeakClass.SpeakCallerInfo("SpeakCallerInfo方法");
            //E:\GameDevelopment\GameDevelopmentLearning\C#\C#进阶\CSharp进阶教程\Lesson21_特性\Program.cs
            //275
            //Main


            #endregion

            #region 知识点七 系统自带特性——条件编译特性

            //因为脚本最前面 定义ConditionalCompilationFun 所以能正常执行 否则执行无效
            ConditionalCompilationFun();//条件编译方法执行

            #endregion

        }
    }

    //总结:

    //特性是用于 为元数据再添加更多的额外信息(变量、方法等等)
    //我们可以通过反射获取这些额外的数据 来进行一些特殊的处理
    //自定义特性——继承Attribute类

    // 系统自带特性:过时特性

    // 为什么要学习特性
    // Unity引擎中很多地方都用到了特性来进行一些特殊处理
}

22.3 练习题

为反射练习题中的类库工程中的Player对象,随便为其中一个成员变量加一个自定义特性,同样实现反射练习题中的要求,但是当在设置加了自定义特性的成员变量时,在控制台中打印一句,非法操作,随意修改XXX成员

修改类库工程 给类库工程加上自定义特性类 为玩家名成员变量添加自定义特性

using System;

namespace MrTao
{
    // 修改类库工程 给类库工程加上自定义特性类 为玩家名成员变量添加自定义特性

    // 我的自定义特性
    class MyCustomAttribute : Attribute
    {

    }

    // 位置
    struct Position
    {
        public int x;
        public int y;
        public Position(int x, int y)
        {
            this.x = x;
            this.y = y;
        }
    }

    // 玩家
    class Player
    {
        // 为玩家名添加自定义特性
        [MyCustom()]
        public string name;

        public int hp;
        public int atk;
        public int def;
        public Position pos;
    }
}

调用Assembly类的LoadFrom方法 加载一个指定程序集

Assembly assembly = Assembly.LoadFrom(@"E:\GameDevelopment\GameDevelopmentLearning\C#\C#进阶\CSharp进阶教程\测试\bin\Debug\测试");
//注意1:传入的路径是dll文件所在的路径 路径的最后要带上dll文件的文件名 也可以加上dll后缀
//注意2:不能直接传入路径 因为\是转义字符 直接输入\不代表\
//取消转义字符有两种方式:
//1.使用\\ 意思是取消转义 这样一个\\就代表一个真正的\
//2.在路径前加一个@

调用Assembly类的GetTypes方法 获得程序集中的所有类的Type

// 用一个Type类数组装载
Type[] types = assembly.GetTypes();

遍历打印程序集中的所有类的Type

for (int i = 0; i < types.Length; i++)
{
    Console.WriteLine(types[i]);
    // MrTao.MyCustomAttribute
    // MrTao.Position
    // MrTao.Player
}

通过反射 实例化一个程序集中的对象(player对象)

i. 调用GetType()方法 通过传入类名 得到MrTao.Player类的Type

// 注意:类名必须包含命名空间 不然找不到
Type playerType = assembly.GetType("MrTao.Player");

ii. 调用Activator类中的CreateInstance()方法

// Activator类中的CreateInstance()方法会返回一个object对象 CreateInstance有多个重载
// 第一个参数固定传 你想要构造的类的Type类 比如 实例化一个icon对象就传入iconType
// 后面的参数是变长参数 根据你想要构造的类 的 构造函数参数 传对应的参数 假如是无参构造函数就可以不传
// Player类只有无参构造函数 所以就可以不传
object playerObject = Activator.CreateInstance(playerType);
// 这样就能实例化player的对象 但是由于Player类不在我们的程序集中 所以没有as成Player 而是用object去装

打印playerObject

Console.WriteLine(playerObject);// MrTao.Player

调用Type类中的GetFields()方法 可以得到MrTao.Player类中的所有公共成员变量

// 要用到FieldInfo类的数组 装载 MrTao.Player类中的所有公共成员变量
// FieldInfo类是公共成员变量的反射信息类
// FieldInfo类需要引用命名空间 using System.Reflection;
FieldInfo[] playerTypeFieldInfo = playerType.GetFields();

遍历打印MrTao.Player类的所有公共成员变量

for (int i = 0; i < playerTypeFieldInfo.Length; i++)
{
    Console.WriteLine(playerTypeFieldInfo[i]);
    // System.String name
    // Int32 hp
    // Int32 atk
    // Int32 def
    // MrTao.Position pos
}

首先要得到我们自定特性的Type

Type myCustomAttributeibuteType = assembly.GetType("MrTao.MyCustomAttribute");

获得name成员变量的公共成员变量的反射信息

FieldInfo playerTypeFieldInfoName = playerType.GetField("name");

判断当前name成员变量有没有特性

// 调用MemberInfo类中的GetCustomAttribute方法 尝试获取MemberInfo元数据中的特性
// 如果有就返回MemberInfo元数据中的特性 没有就返回空
// 参数一:我们自定特性的Type
// 注意:知识点提到的Type类中的GetCustomAttributes方法 其实Type是继承MemberInfo的 FieldInfo也继承MemberInfo的

// 得到的特性如果不为空 就证明有这个特性
if (playerTypeFieldInfoName.GetCustomAttribute(myCustomAttributeibuteType) != null)
{
    Console.WriteLine("非法操作,随意修改name成员");// 非法操作,随意修改name成员
}
// 检测没有被自定义特性修饰 可以修改
else
{
    // 调用FieldInfo类中 name成员变量的公共成员变量的反射信息类 的SetValue()方法 传入MrTao.Player类的对象和要设置的值
    // 注意:要先得到用FieldInfo类装的指定名称的公共成员变量的反射信息
    playerTypeFieldInfoName.SetValue(playerObject, "123123");
}

22.4 练习题代码

using System;
using System.Reflection;

namespace Lesson21_练习题
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("特性练习题");

            #region 练习题一
            //为反射练习题中的类库工程中的Player对象
            //随便为其中一个成员变量加一个自定义特性
            //同样实现反射练习题中的要求

            //但是当在设置加了自定义特性的成员变量时,在控制台中打印一句
            //非法操作,随意修改XXX成员


            //调用Assembly类的LoadFrom方法 加载一个指定程序集
            Assembly assembly = Assembly.LoadFrom(@"E:\GameDevelopment\GameDevelopmentLearning\C#\C#进阶\CSharp进阶教程\测试\bin\Debug\测试");
            //注意1:传入的路径是dll文件所在的路径 路径的最后要带上dll文件的文件名 也可以加上dll后缀
            //注意2:不能直接传入路径 因为\是转义字符 直接输入\不代表\
            //取消转义字符有两种方式:
            //1.使用\\ 意思是取消转义 这样一个\\就代表一个真正的\
            //2.在路径前加一个@

            //调用Assembly类的GetTypes方法 获得程序集中的所有类的Type
            //用一个Type类数组装载
            Type[] types = assembly.GetTypes();


            //遍历打印程序集中的所有类的Type
            for (int i = 0; i < types.Length; i++)
            {
                Console.WriteLine(types[i]);
                //MrTao.MyCustomAttribute
                //MrTao.Position
                //MrTao.Player
            }

            //通过反射 实例化一个程序集中的对象(player对象)

            //调用GetType()方法 通过传入类名 得到MrTao.Player类的Type
            //注意:类名必须包含命名空间 不然找不到
            Type playerType = assembly.GetType("MrTao.Player");


            //通过反射 实例化一个player对象
            //调用Activator类中的CreateInstance()方法 
            //Activator类中的CreateInstance()方法会返回一个object对象 CreateInstance有多个重载
            //第一个参数固定传 你想要构造的类的Type类 比如 实例化一个icon对象就传入iconType
            //后面的参数是变长参数 根据你想要构造的类 的 构造函数参数 传对应的参数 假如是无参构造函数就可以不传
            //Player类只有无参构造函数 所以就可以不传
            object playerObject = Activator.CreateInstance(playerType);
            //这样就能实例化player的对象 但是由于Player类不在我们的程序集中 所以没有as成Player 而是用object去装

            //打印playerObject
            Console.WriteLine(playerObject);//MrTao.Player




            //调用Type类中的GetFields()方法 可以得到MrTao.Player类中的所有公共成员变量
            //要用到FieldInfo类的数组 装载 MrTao.Player类中的所有公共成员变量
            //FieldInfo类是公共成员变量的反射信息类
            //FieldInfo类需要引用命名空间 using System.Reflection;
            FieldInfo[] playerTypeFieldInfo = playerType.GetFields();

            //遍历打印MrTao.Player类的所有公共成员变量
            for (int i = 0; i < playerTypeFieldInfo.Length; i++)
            {
                Console.WriteLine(playerTypeFieldInfo[i]);
                //System.String name
                //Int32 hp
                //Int32 atk
                //Int32 def
                //MrTao.Position pos
            }

            //首先要得到我们自定特性的Type
            Type myCustomAttributeibuteType = assembly.GetType("MrTao.MyCustomAttribute");

            //获得name成员变量的公共成员变量的反射信息类
            FieldInfo playerTypeFieldInfoName = playerType.GetField("name");


            //判断当前name成员变量有没有特性

            //调用MemberInfo类中的GetCustomAttribute方法 尝试获取MemberInfo元数据中的特性
            //如果有就返回MemberInfo元数据中的特性 没有就返回空
            //参数一:我们自定特性的Type
            //注意:知识点提到的Type类中的GetCustomAttributes方法 其实Type是继承MemberInfo的 FieldInfo也继承MemberInfo的

            //得到的特性如果不为空 就证明有这个特性
            if (playerTypeFieldInfoName.GetCustomAttribute(myCustomAttributeibuteType) != null)
            {
                Console.WriteLine("非法操作,随意修改name成员");//非法操作,随意修改name成员
            }
            //检测没有被自定义特性修饰 可以修改
            else
            {
                //调用FieldInfo类中 name成员变量的公共成员变量的反射信息类 的SetValue()方法 传入MrTao.Player类的对象和要设置的值
                //注意:要先得到用FieldInfo类装的指定名称的公共成员变量的反射信息
                playerTypeFieldInfoName.SetValue(playerObject, "123123");
            }


            #endregion
        }
    }
}

22.5 类库工程代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MrTao
{

    //修改类库工程 给类库工程加上自定义特性类 为玩家名成员变量添加自定义特性

    //我的自定义特性
    class MyCustomAttribute : Attribute
    {

    }

    //位置
    struct Position
    {
        public int x;
        public int y;
        public Position(int x, int y)
        {
            this.x = x;
            this.y = y;
        }
    }

    //玩家
    class Player
    {
        //为玩家名添加自定义特性
        [MyCustom()]
        public string name;

        public int hp;
        public int atk;
        public int def;
        public Position pos;
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏