.NET 6如何通过反射判断泛型字符串参数的可空性
.NET 6 反射判断闭构造泛型参数可空性
结论:完全可以识别。
你提到的NullabilityInfoContext确实只封装了属性、字段、参数、事件这类成员的可空性读取逻辑,没有提供针对裸Type泛型参数的直接接口,但泛型参数的可空性标记是编译期写入程序集元数据的,直接解析元数据里的可空特性就能实现判断。
注意:
List<string>和List<string?>在运行时是两个不同的闭构造泛型类型,但它们的泛型实参底层都是同一个string类型引用,可空性是通过独立的NullableAttribute元数据标记的,不会修改类型本身。
实现原理
C# 编译器会在编译时给带可空引用类型标注的位置注入两个特殊特性:
System.Runtime.CompilerServices.NullableAttribute:标记具体位置的可空状态,1代表非空,2代表可空System.Runtime.CompilerServices.NullableContextAttribute:标记程序集/类型级别的默认可空上下文
对于List<string?>这类闭构造泛型,可空标记会按泛型参数的顺序挂载在泛型类型本身的特性上,直接按位置读取标记值即可。
可运行示例代码
using System.Reflection; using System.Collections.ObjectModel; // 测试用例 Type nrvt = typeof(List<string?>); Type nnrvt = typeof(List<string>); Console.WriteLine(IsGenericArgNullable(nrvt, 0)); // 输出:True Console.WriteLine(IsGenericArgNullable(nnrvt, 0)); // 输出:False static bool IsGenericArgNullable(Type closedGenericType, int argIndex) { // 入参校验:必须是闭构造泛型 if (!closedGenericType.IsGenericType || closedGenericType.IsGenericTypeDefinition) throw new ArgumentException("输入必须是已构造完成的闭泛型类型"); Type[] genericArgs = closedGenericType.GetGenericArguments(); if (argIndex < 0 || argIndex >= genericArgs.Length) throw new ArgumentOutOfRangeException(nameof(argIndex)); Type targetArg = genericArgs[argIndex]; // 值类型直接判断是否为Nullable<T>即可 if (targetArg.IsValueType) return Nullable.GetUnderlyingType(targetArg) != null; // 1. 优先读取泛型类型本身挂载的可空特性 var typeNullableAttr = closedGenericType.GetCustomAttributesData() .FirstOrDefault(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.NullableAttribute"); if (typeNullableAttr != null) { var arg = typeNullableAttr.ConstructorArguments[0]; // 单字节值代表所有泛型参数共用同一个标记 if (arg.ArgumentType == typeof(byte)) return (byte)arg.Value! == 2; // 字节数组代表按泛型参数顺序存储标记 var flags = (ReadOnlyCollection<CustomAttributeTypedArgument>)arg.Value!; return (byte)flags[argIndex].Value! == 2; } // 2. 类型无标记则读取对应泛型形参的默认标记 var genericTypeDef = closedGenericType.GetGenericTypeDefinition(); var genericParam = genericTypeDef.GetGenericArguments()[argIndex]; var paramNullableAttr = genericParam.GetCustomAttributesData() .FirstOrDefault(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.NullableAttribute"); if (paramNullableAttr != null) return (byte)paramNullableAttr.ConstructorArguments[0].Value! == 2; // 3. 兜底读取程序集级别的可空上下文 var assemblyCtxAttr = closedGenericType.Assembly.GetCustomAttributesData() .FirstOrDefault(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.NullableContextAttribute"); if (assemblyCtxAttr != null) return (byte)assemblyCtxAttr.ConstructorArguments[0].Value! == 2; // 无任何可空标记时默认视为非空 return false; }
注意事项
- 不要尝试直接对
GetGenericArguments()返回的string类型读取可空特性:string和string?运行时是完全同一个Type对象,可空标记是挂在声明这个泛型实例的宿主类型上,不是挂在实参类型本身上。 - 上述逻辑和
NullabilityInfoContext内部的元数据解析逻辑完全一致,只是没有封装泛型参数的场景入口而已。 - 如果是嵌套泛型(比如
List<List<string?>>),需要递归解析每一层泛型的可空标记,逻辑和上述实现一致。
内容的提问来源于stack exchange,提问作者CodeAngry
相关产品推荐
相关产品推荐

