.NET进阶——深入理解反射(4)利用反射获取信息(方法、特性)

一、概述和前景提要

由于上文已经详细介绍过如何利用Type类型获取类信息、属性信息、字段信息的相关知识点,不明白的小伙伴请移步上文:.NET进阶------深入理解反射(3)利用反射获取信息(对象、属性、字段)一、概述和前景提要 由于上文已经详细介绍过如何获取 - 掘金

本文分两个大点,分别是:

  • 通过Type类型获取方法信息
  • 通过Type类型获取特性信息

二、通过 Type 类型获取方法信息

MethodInfo 是反射中描述方法成员的核心类,继承自 MemberInfo,用于封装方法的元数据(如名称、参数、返回值、访问修饰符等),并支持动态调用方法。结合 Type 类的方法,可灵活获取任意类型的方法信息。

2.1 MethodInfo 核心属性与方法

关键属性(描述方法元数据)

属性 说明
Name 获取方法名称(如 ShowStudy)。
ReturnType 获取方法的返回值类型(如 CustomContextvoid)。
IsPublic/IsPrivate/IsProtected 判断方法的访问修饰符。
IsStatic 判断方法是否为静态方法。
IsInstance 判断方法是否为实例方法(非静态)。
IsGenericMethod 判断方法是否为泛型方法(如 T GetValue<T>())。
IsGenericMethodDefinition 判断方法是否为泛型方法定义(未指定具体泛型参数,如 List<T>.Add(T))。
GetParameters() 获取方法的参数列表,返回 ParameterInfo[](包含参数名称、类型、默认值等)。
DeclaringType 获取声明该方法的类型(如方法所属的类)。

关键方法(操作方法)

方法 说明
Invoke(object obj, object[] parameters) 动态调用方法:- 实例方法:obj 传入实例对象,parameters 传入参数数组(无参数传 null);- 静态方法:objnull;- 返回值为方法的执行结果(void 方法返回 null)。
MakeGenericMethod(Type[] typeArgs) 为泛型方法定义指定具体类型参数,返回可调用的泛型方法实例(如 GetDefault<int>())。
GetGenericArguments() 获取泛型方法的类型参数(如 T),返回 Type[]
GetCustomAttribute<T>(bool inherit) 获取方法上的指定类型特性(继承自 MemberInfo)。
IsDefined(Type attributeType, bool inherit) 检查方法是否应用了指定类型的特性(继承自 MemberInfo)。
GetParameters() 获取方法的所有参数信息,包括参数名称、类型、是否为输出参数等。

2.2 实战示例:获取方法信息

示例 1:获取单个指定方法

支持获取公共 / 私有、静态 / 实例、带参数 / 无参数的方法,需通过 BindingFlags 筛选条件。

cs 复制代码
// 定义测试类(含多种方法类型)
public class UserService
{
    // 公共无参数实例方法
    public string GetUserName() => "默认用户";
    
    // 公共带参数实例方法
    public int CalculateAge(int birthYear, int currentYear) => currentYear - birthYear;
    
    // 私有静态方法
    private static string GetPrivateMessage() => "私有静态方法的返回值";
    
    // 泛型方法
    public T GetDefaultValue<T>() => default(T);
}

Type serviceType = typeof(UserService);
UserService serviceInstance = new UserService();

// 1. 获取公共无参数方法 GetUserName
MethodInfo getUserNameMethod = serviceType.GetMethod("GetUserName");
Console.WriteLine($"方法名:{getUserNameMethod.Name}");
Console.WriteLine($"返回值类型:{getUserNameMethod.ReturnType.Name}");
// 动态调用方法(无参数)
string userName = (string)getUserNameMethod.Invoke(serviceInstance, null);
Console.WriteLine($"调用结果:{userName}\n");

// 2. 获取带参数方法 CalculateAge(需匹配参数类型)
Type[] paramTypes = new[] { typeof(int), typeof(int) };
MethodInfo calculateAgeMethod = serviceType.GetMethod("CalculateAge", paramTypes);
Console.WriteLine($"带参数方法:{calculateAgeMethod.Name}");
// 动态传入参数调用
int age = (int)calculateAgeMethod.Invoke(serviceInstance, new object[] { 2000, 2024 });
Console.WriteLine($"调用结果:{age}\n");

// 3. 获取私有静态方法 GetPrivateMessage(需指定 BindingFlags)
MethodInfo privateStaticMethod = serviceType.GetMethod(
    "GetPrivateMessage", 
    BindingFlags.NonPublic | BindingFlags.Static
);
// 静态方法调用:obj参数传null
string privateMsg = (string)privateStaticMethod.Invoke(null, null);
Console.WriteLine($"私有静态方法调用结果:{privateMsg}\n");

// 4. 获取并调用泛型方法 GetDefaultValue<T>
MethodInfo genericMethodDef = serviceType.GetMethod("GetDefaultValue");
// 指定泛型参数为int,创建可调用的泛型方法实例
MethodInfo intDefaultMethod = genericMethodDef.MakeGenericMethod(typeof(int));
int intDefault = (int)intDefaultMethod.Invoke(serviceInstance, null);
Console.WriteLine($"int默认值:{intDefault}");

// 指定泛型参数为string
MethodInfo stringDefaultMethod = genericMethodDef.MakeGenericMethod(typeof(string));
string stringDefault = (string)stringDefaultMethod.Invoke(serviceInstance, null);
Console.WriteLine($"string默认值:{stringDefault ?? "null"}");

输出结果:

csharp 复制代码
方法名:GetUserName
返回值类型:String
调用结果:默认用户

带参数方法:CalculateAge
调用结果:24

私有静态方法调用结果:私有静态方法的返回值

int默认值:0
string默认值:null

示例 2:获取多个方法(筛选条件)

通过 Type.GetMethods() 结合 BindingFlags,可批量获取符合条件的方法(如所有公共实例方法、所有静态方法等)。

cs 复制代码
Type serviceType = typeof(UserService);

// 1. 获取所有公共实例方法(默认行为,含继承自object的方法)
MethodInfo[] publicInstanceMethods = serviceType.GetMethods();
Console.WriteLine("所有公共实例方法:");
foreach (var method in publicInstanceMethods)
{
    // 过滤掉继承自object的方法(可选)
    if (method.DeclaringType == serviceType)
    {
        string paramStr = string.Join(", ", method.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}"));
        Console.WriteLine($"- {method.ReturnType.Name} {method.Name}({paramStr})");
    }
}

// 2. 获取所有静态方法(含公共+私有)
MethodInfo[] allStaticMethods = serviceType.GetMethods(
    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static
);
Console.WriteLine("\n所有静态方法:");
foreach (var method in allStaticMethods)
{
    string accessModifier = method.IsPublic ? "public" : "private";
    Console.WriteLine($"- {accessModifier} static {method.ReturnType.Name} {method.Name}");
}

输出结果:

scss 复制代码
所有公共实例方法:
- String GetUserName()
- Int32 CalculateAge(Int32 birthYear, Int32 currentYear)
- T GetDefaultValue<T>()

所有静态方法:
- private static String GetPrivateMessage()

示例 3:获取方法的特性信息

结合 MemberInfo 的特性相关方法,可获取方法上应用的自定义特性(如之前中间件示例中的 AbstractMiddleWareAttribute)。

cs 复制代码
// 定义自定义特性(呼应之前的中间件场景)
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class LogAttribute : Attribute
{
    public string Description { get; set; }
    public bool NeedLog { get; set; } = true;
}

// 应用特性的测试方法
public class OrderService
{
    [Log(Description = "创建订单", NeedLog = true)]
    public void CreateOrder(string orderNo) { }
    
    [Log(Description = "取消订单", NeedLog = false)]
    [Log(Description = "记录取消操作", NeedLog = true)] // 允许多个特性
    public void CancelOrder(string orderNo) { }
}

Type orderType = typeof(OrderService);
MethodInfo createOrderMethod = orderType.GetMethod("CreateOrder");

// 1. 检查方法是否应用了LogAttribute
bool hasLogAttr = createOrderMethod.IsDefined(typeof(LogAttribute), inherit: false);
Console.WriteLine($"CreateOrder方法是否有Log特性:{hasLogAttr}");

// 2. 获取单个LogAttribute(泛型方法,简洁)
LogAttribute logAttr = createOrderMethod.GetCustomAttribute<LogAttribute>();
if (logAttr != null)
{
    Console.WriteLine($"特性描述:{logAttr.Description}");
    Console.WriteLine($"是否需要日志:{logAttr.NeedLog}");
}

// 3. 获取多个特性(AllowMultiple = true时)
MethodInfo cancelOrderMethod = orderType.GetMethod("CancelOrder");
IEnumerable<LogAttribute> logAttrs = cancelOrderMethod.GetCustomAttributes<LogAttribute>();
Console.WriteLine("\nCancelOrder方法的所有Log特性:");
foreach (var attr in logAttrs)
{
    Console.WriteLine($"- 描述:{attr.Description},是否日志:{attr.NeedLog}");
}

输出结果:

python 复制代码
CreateOrder方法是否有Log特性:True
特性描述:创建订单
是否需要日志:True

CancelOrder方法的所有Log特性:
- 描述:取消订单,是否日志:False
- 描述:记录取消操作,是否日志:True

三、通过 Type 类型获取特性信息

特性(Attribute是.NET 中用于为类型、成员(方法、属性、字段等)添加元数据的机制,反射可动态读取这些元数据,实现 "配置驱动" 或 "AOP" 等场景。获取特性的核心是 MemberInfo 类的相关方法(TypeMethodInfoPropertyInfo 等均继承自 MemberInfo)。

3.1 特性获取的核心方法

方法 说明
GetCustomAttribute<T>(bool inherit) 泛型版本(推荐):获取成员上指定类型的第一个 特性;- inherit = true:会查找继承自父类的特性;- 未找到返回 null
GetCustomAttributes<T>(bool inherit) 获取成员上指定类型的所有 特性,返回 IEnumerable<T>
GetCustomAttributes(bool inherit) 获取成员上的所有 特性,返回 object[](需强制类型转换)。
IsDefined(Type attributeType, bool inherit) 检查成员是否应用了指定类型的特性,返回 bool(性能优于先获取再判断)。

3.2 实战示例:获取不同层级的特性

示例 1:获取类型上的特性

特性可直接应用于类、接口、枚举等类型,通过 Type 实例直接获取。

cs 复制代码
// 定义应用于类型的特性
[AttributeUsage(AttributeTargets.Class)]
public class ModuleAttribute : Attribute
{
    public string Name { get; set; }
    public string Version { get; set; }
}

// 应用特性的类
[Module(Name = "用户模块", Version = "1.0.0")]
public class UserModule { }

Type moduleType = typeof(UserModule);

// 1. 检查类型是否有ModuleAttribute
bool hasModuleAttr = moduleType.IsDefined(typeof(ModuleAttribute), inherit: false);
Console.WriteLine($"UserModule是否有Module特性:{hasModuleAttr}");

// 2. 获取类型上的ModuleAttribute
ModuleAttribute moduleAttr = moduleType.GetCustomAttribute<ModuleAttribute>();
if (moduleAttr != null)
{
    Console.WriteLine($"模块名称:{moduleAttr.Name}");
    Console.WriteLine($"模块版本:{moduleAttr.Version}");
}

输出结果:

cs 复制代码
UserModule是否有Module特性:True
模块名称:用户模块
模块版本:1.0.0

示例 2:获取属性上的特性

常用于 ORM 映射、API 验证等场景(如 EF Core 列名映射、Swagger 参数描述)。

cs 复制代码
// 定义属性特性
[AttributeUsage(AttributeTargets.Property)]
public class ColumnAttribute : Attribute
{
    public string Name { get; set; } // 数据库列名
    public bool IsPrimaryKey { get; set; } // 是否为主键
}

// 应用特性的实体类
public class User
{
    [Column(Name = "user_id", IsPrimaryKey = true)]
    public int Id { get; set; }
    
    [Column(Name = "user_name")]
    public string Name { get; set; }
    
    public int Age { get; set; } // 无特性
}

Type userType = typeof(User);
PropertyInfo[] properties = userType.GetProperties();

Console.WriteLine("User类属性的Column特性:");
foreach (var prop in properties)
{
    ColumnAttribute columnAttr = prop.GetCustomAttribute<ColumnAttribute>();
    if (columnAttr != null)
    {
        Console.WriteLine($"属性:{prop.Name}");
        Console.WriteLine($"- 列名:{columnAttr.Name}");
        Console.WriteLine($"- 是否主键:{columnAttr.IsPrimaryKey}\n");
    }
    else
    {
        Console.WriteLine($"属性:{prop.Name} → 无Column特性\n");
    }
}

输出结果:

sql 复制代码
User类属性的Column特性:
属性:Id
- 列名:user_id
- 是否主键:True

属性:Name
- 列名:user_name
- 是否主键:False

属性:Age → 无Column特性

示例 3:处理继承的特性

当特性设置 Inherited = true(默认)时,子类可继承父类的特性,通过 inherit = true 可获取。

cs 复制代码
// 父类(应用特性)
[Module(Name = "基础模块", Version = "0.1.0")]
public class BaseModule { }

// 子类(不重新应用特性,继承父类)
public class OrderModule : BaseModule { }

Type orderModuleType = typeof(OrderModule);

// 1. inherit = true:获取继承的特性
ModuleAttribute inheritedAttr = orderModuleType.GetCustomAttribute<ModuleAttribute>(inherit: true);
Console.WriteLine("继承的特性:");
Console.WriteLine($"模块名称:{inheritedAttr.Name}");
Console.WriteLine($"模块版本:{inheritedAttr.Version}\n");

// 2. inherit = false:仅获取自身的特性(无)
ModuleAttribute selfAttr = orderModuleType.GetCustomAttribute<ModuleAttribute>(inherit: false);
Console.WriteLine($"自身是否有Module特性:{selfAttr != null}");

输出结果:

sql 复制代码
继承的特性:
模块名称:基础模块
版本:0.1.0

自身是否有Module特性:False

3.3 特性获取的关键注意事项

  1. AttributeUsage 限制 :特性的应用范围(如仅类、仅方法)由 AttributeTargets 指定,超出范围的特性无法通过反射获取。
  2. AllowMultiple 配置 :当特性设置 AllowMultiple = true 时,需用 GetCustomAttributes 获取所有特性,GetCustomAttribute 仅返回第一个。
  3. 性能考量 :反射获取特性的性能开销较低,但频繁调用时建议缓存结果(如缓存 PropertyInfo 与特性的映射关系)。
  4. 继承特性的生效条件 :父类特性需设置 Inherited = true(默认值),子类才能通过 inherit = true 获取;接口的特性默认不被类继承。

三、总结

反射获取方法与特性信息是.NET 框架开发的核心技能之一,其核心价值在于动态性------ 无需编译时知道具体类型或成员,即可在运行时获取元数据并操作。

相关推荐
by__csdn2 小时前
第一章 (ASP.NET Core入门)第一节( 认识.NET Core)
后端·c#·asp.net·.net·.netcore·f#·vb.net
by__csdn2 小时前
第一章 (ASP.NET Core入门)第二节( 认识ASP.NET Core)
数据库·后端·c#·asp.net·.net·.netcore·f#
缺点内向2 小时前
如何使用C#将Excel工作表拆分为独立文件
开发语言·c#·.net·excel
唐青枫4 小时前
一次看懂 C# TimeSpan:时间差操作的完整指南
c#·.net
Crazy Struggle20 小时前
.NET 8 微服务框架长什么样?集成 AI 智能体、多租户、自动调度与实时通信
微服务·.net·.net 8.0
玩泥巴的1 天前
一分钟实现.NET与飞书长连接的WebSocket架构
c#·.net·二次开发·飞书
mudtools1 天前
一分钟实现.NET与飞书长连接的WebSocket架构
后端·c#·.net
SEO-狼术1 天前
FastGrid delivers clean Crack
.net
一个帅气昵称啊2 天前
.Net使用AgentFramework进行多Agent工作流编排-智能体AI开发
c#·.net·agentframework