为何ISymbol.ContainingNamespace对可为空结构体始终返回System命名空间?
这其实是Roslyn对可为空值类型和可为空引用类型的语义处理差异导致的,我来给你掰扯清楚:
当你处理SomeStruct?这种可为空结构体时,它在语义层面并不是原结构体类型加个可空标记,而是被包装成了System.Nullable<SomeStruct>这个泛型类型。你拿到的ITypeSymbol其实是Nullable<T>的构造泛型实例,而Nullable<T>本身就属于System命名空间,所以调用ContainingNamespace自然会返回System。
但可为空类(比如SomeClass?)就不一样了——引用类型的可空是通过可空注解实现的,它的ITypeSymbol本质还是原来的SomeClass类型,只是附带了可空的元数据信息,所以ContainingNamespace会正常返回类所在的命名空间。非空结构体就更不用说了,直接就是原类型的符号,结果自然符合预期。
怎么解决这个问题?
你需要先识别出当前类型是不是Nullable<T>的实例,然后拆解出它的泛型参数(也就是原来的结构体类型),再去获取这个参数的命名空间。另外还要注意数组类型的情况,ITypeSymbol.ContainingNamespace对数组会返回null,同样需要先拿到数组的元素类型再处理。如果遇到嵌套场景(比如SomeStruct?[][]这种),还要一层层递归拆解。
比如你可以写一个工具方法来处理这些情况:
public static INamespaceSymbol GetActualContainingNamespace(ITypeSymbol typeSymbol) { while (true) { // 处理数组类型:获取元素类型 if (typeSymbol.Kind == SymbolKind.ArrayType) { typeSymbol = ((IArrayTypeSymbol)typeSymbol).ElementType; continue; } // 处理Nullable<T>:获取泛型参数T if (typeSymbol.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) { typeSymbol = typeSymbol.TypeArguments[0]; continue; } // 已经是原始类型,返回它的命名空间 return typeSymbol.ContainingNamespace; } }
把你代码里的最后一行改成调用这个方法:
Console.WriteLine(GetActualContainingNamespace(someNullableStructField.Type));
这样就能得到正确的StructNamespaceRepro.SomeNamespace了。
这里再给大家提个醒:做类似类型处理时,一定要区分开Roslyn对值类型可空和引用类型可空的不同实现,同时别忘了数组、嵌套数组这类特殊场景,都要针对性拆解处理。
内容来源于stack exchange

