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]
- CallerFilePath特性:
- 哪一行调用?
- CallerLineNumber特性:
[CallerLineNumber]
- CallerLineNumber特性:
- 哪个函数调用?
- CallerMemberName特性:
[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