c# - 发现泛型参数的 NullabilityInfo?
问题描述
我已成功阅读并使用 NullableAttribute/NullableContextAttribute(基于https://github.com/dotnet/roslyn/blob/main/docs/features/nullable-metadata.md和太多的试验和错误)用于属性、字段和方法参数。
但是,我未能分析泛型参数的可空性。我的目标是获取 IA 类型参数(元组)的可空性信息:
interface ICommand<T> { }
interface IA : ICommand<(string,List<string?>?)> { }
但是,Nullable 和 NullableContext 属性显然是不够的。下面的代码将这些属性转储到类型树上:
[Test]
public void type_in_generic_definition()
{
var b = new StringBuilder();
Dump( typeof( IA ), b, Environment.NewLine );
Dump( typeof( IA ).GetInterfaces()[0], b, Environment.NewLine );
Console.WriteLine( b );
}
void Dump( Type type, StringBuilder w, string newline )
{
newline = newline + " ";
w.Append( type.Name ).Append( newline );
var p = GetNullableProfile( type );
w.Append( $"Nullable: {(p != null ? string.Join( null, p.Select( v => v.ToString() ) ) : "null")}" ).Append( newline );
var c = GetNullableContextValue( type );
w.Append( $"NullableContext: {(c != null ? c.ToString() : "null")}" ).Append( newline );
var d = GetNullableContextValueFromDeclaringType( type );
w.Append( $"NullableContext (DeclaringType): {(d != null ? d.ToString() : "null")}" ).Append( newline );
if( type.IsGenericType )
{
foreach( var sub in type.GetGenericArguments() )
{
Dump( sub, w, newline );
}
}
newline = newline.Substring( 0, newline.Length - 2 );
w.Append( newline );
}
byte[]? GetNullableProfile( Type t )
{
var a = t.CustomAttributes.FirstOrDefault( a => a.AttributeType.Name == "NullableAttribute" && a.AttributeType.Namespace == "System.Runtime.CompilerServices" );
if( a == null ) return null;
object? data = a.ConstructorArguments[0].Value;
Debug.Assert( data != null );
if( data is byte b ) return new[] { b };
return ((IEnumerable<CustomAttributeTypedArgument>)data).Select( a => (byte)a.Value! ).ToArray();
}
byte? GetNullableContextValue( Type t )
{
var a = t.CustomAttributes.FirstOrDefault( a => a.AttributeType.Name == "NullableContextAttribute" && a.AttributeType.Namespace == "System.Runtime.CompilerServices" );
return a == null
? null
: (byte)a.ConstructorArguments[0].Value!;
}
byte? GetNullableContextValueFromDeclaringType( Type t )
{
var parent = t.DeclaringType;
while( parent != null )
{
var found = GetNullableContextValue( parent );
if( found.HasValue ) return found;
parent = parent.DeclaringType;
}
return null;
}
这给了我这个描述(至少对我而言)对于(string,List<string?>?)
(我已经在 // 之后的行注释)的实际可空性没有任何意义。
IA
Nullable: null
NullableContext: null
NullableContext (DeclaringType): 1 // I'm working in a NRT aware context where
// reference types are by default not annotated
// (not null). Ok.
ICommand`1
Nullable: null
NullableContext: 2 // Q0: On what scope does this apply?
NullableContext (DeclaringType): 1 // Ok. It's my context.
ValueTuple`2
Nullable: null // Ouch! I was expecting a "122" here :(
NullableContext: null
NullableContext (DeclaringType): null
String
Nullable: 0 // Q1: Explicit oblivious here. Was expecting 1!
NullableContext: 1 // Q2: Scope? Is it the 1 I'm after?
NullableContext (DeclaringType): null
List`1
Nullable: 0 // The same as the string!
NullableContext: 1 // But the list IS nullable!
NullableContext (DeclaringType): null
String
Nullable: 0 // The inner string is nullable :(
NullableContext: 1
NullableContext (DeclaringType): null
我应该在哪里寻找这个?我错过了什么?
解决方案
和属性仅供编译器使用,Nullable
这意味着要使用这些属性,至少以任何有意义的方式是如果您使用 roslyn 源代码进行编程(又名。对编译器本身进行编程)。NullableContext
如果您没有对编译器进行编程
请记住,可空性几乎是严格意义上的编译器时间概念。这意味着它基本上只是存在,因此编译器可以通过启发式和其他魔法来确定您正在以一种可以在运行时阻止的方式使用类型NullReferenceExceptions
,以及可读性等其他好处。
这并不是说我们无法确定一个类型是否Nullable<>
在运行时。为此,我们必须记住一些关于如何Nullable<>
工作的规则。
可空类型是struct
具有底层不可空类型(Nullable<>.Value
)的。a 的基础类型Nullable<T>
不能是可为空的类型或引用类型。
因此,在运行时,要确定任何给定类型是否Nullable<>
存在,我们只需检查是否可以提取底层类型。
byte? GetNullableContextValue(Type t)
{
// if it's a struct, and it's a Nullable<> then we will be able to extract the underlying type
if (Nullable.GetUnderlyingType(t) != null)
{
return 1;
}
// the type was a class, object, or struct not wrapped in a Nullable<>
return 0;
}
这意味着不能将类包装在Nullable<>
结构中。这可能看起来很违反直觉,因为没有什么可以阻止您CustomClass? variable = GetPossiblyNullCustomClass();
在源代码中使用。
它仍然允许您这样做的原因有几个原因,但主要是一个原因,因此编译器可以有效地跟踪源代码中对象的路径并通知您可能发生的空引用异常(例如,如果您在许多其他类型的启发式检查中忘记初始化变量)。在完成这些编译时检查并编译源代码后,任何内容CustomClass?
都将被忽略,CustomClass
而是被使用。
我确信有一些非常酷的 hacky 方法可以找到它,但是在运行时通过反射我不相信有一种方法可以确定某些class
是否是class?
.
如果你正在编程 Rosyln
确保您可以System.Runtime.CompilerServices
在您的范围内访问,您可以通过导入 roslyn 编译器的源代码并在项目中添加对它的引用来做到这一点。要访问可空性标志,只需通过反射直接在属性上访问它,这是一个简短的示例,说明您可以如何做到这一点:
NullableContextAttribute? foundAttribute = type.GetCustomAttribute<NullableContextAttribute>();
switch(foundAttribute.Flag)
{
case 0:
case 1:
case 2:
// do work;
break;
default:
throw new Exception("Flag out of bounds.");
}
推荐阅读
- c++ - 即使我确定指定的路径是正确的,QTranslator 似乎也找不到我的 .ls 文件
- sql - 如何使用 EXIST 报告已完成工作支付的所有发票与未结发票?
- angular - 反应形式在 Ionic Modal 的视图中不显示值
- python - 在 Mac OS X 上使用 Jupyter Notebook 没有名为“pandas”的模块问题
- ios - NavigationView 变为空白,从导航链接的详细视图重复转换
- c++ - Cleaning code for move generator function for game in c++
- javascript - MongoDB在对象数组中查找匹配文档并更新匹配字段
- vue.js - 为什么 Jest spyOn() 只通过了其中一个测试,而在另一个测试中失败了?
- kubernetes - external.metrics.k8s.io 在 EKS 集群中不可用
- c# - 如何显示动画?