You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Framework反射获取XML注释成员名时Type.GetGenericArguments结果异常

问题背景

我们有一个基于.NET Framework的遗留WinForms应用,其中包含一个从配套XML注释文件读取程序集文档的模块。该代码多年运行正常,但近期发现一处缺陷。

复现问题的类代码如下:

public class Class1<T1, T2>
{
    public struct MyStruct<TS1, TS2>
    {
        public TS1 StructField1;
    }

    public void Method1(T1 arg1)
    { }
    public void Method2(T1 arg1, T2 arg2)
    { }
    public void Method3(MyStruct<string, T1> arg1)
    { }
    public void Method4(MyStruct<T2, int> arg1)
    { }
}

Method3和Method4在XML注释文件中的记录:

<member name="M:TestSimple.Class1`2.Method3(TestSimple.Class1{`0,`1}.MyStruct{System.String,`0})">
    <summary>
    Method3 summary.
    </summary>
    <param name="arg1">Argument #1.</param>
</member>
<member name="M:TestSimple.Class1`2.Method4(TestSimple.Class1{`0,`1}.MyStruct{`1,System.Int32})">
    <summary>
    Method4 summary.
    </summary>
    <param name="arg1">Argument #1.</param>
</member>

要访问这些注释,需要为Method3和Method4分别生成如下字符串:

M:TestSimple.Class1`2.Method3(TestSimple.Class1{`0,`1}.MyStruct{System.String,`0})

和

M:TestSimple.Class1`2.Method4(TestSimple.Class1{`0,`1}.MyStruct{`1,System.Int32})

在构造该字符串的反射代码中,处理MyStruct的参数描述字符串时出现问题:.NET的Type.GetGenericArguments()方法返回的泛型参数列表不正确——MyStruct应返回2个参数,但实际返回4个。即使按照文档使用Type.IsGenericParameter过滤也无效,Method3的参数列表中T1重复,Method4中T2重复。

当前使用的GetGenericClosedConstructedTypeArgumentsString方法实现:

private static string GetGenericClosedConstructedTypeArgumentsString(Type type)
{
    Type[] myArgumentTypes = type.GetGenericArguments();

    StringBuilder myResult = new StringBuilder();

    myResult.Append(ConstsXmlHelp.cGenericMethodParameterArgumentListStart);

    foreach (Type myArgumentType in myArgumentTypes)
    {
        if (myArgumentType.IsGenericParameter)
        {
            if (myArgumentType.DeclaringType != null && myArgumentType.DeclaringType != type)
            {
                #region Check whether the current argument type is declared in an enclosing type

                bool myIsArgumentTypeDeclaredInEnclosingType = false;
                Type myEnclosingType = type.DeclaringType;
                while (myEnclosingType != null)
                {
                    if (myEnclosingType == myArgumentType.DeclaringType)
                    {
                        myIsArgumentTypeDeclaredInEnclosingType = true;
                        break;
                    }

                    myEnclosingType = myEnclosingType.DeclaringType;
                } 

                #endregion

                if (myIsArgumentTypeDeclaredInEnclosingType)
                    continue;
            }
        }

        if (myResult.Length > ConstsXmlHelp.cGenericMethodParameterArgumentListStart.Length)
            myResult.Append(ConstsXmlHelp.cParameterDelimeter);

        // GetFullName() below returns the generic or string
        // argument equivalent like `0, `1, or System.Int32
        myResult.Append(GetFullName(myArgumentType));
    }

    myResult.Append(ConstsXmlHelp.cGenericMethodParameterArgumentListEnd);

    return myResult.ToString();
}

internal static class ConstsXmlHelp
{
    public const string cParameterDelimeter = ",";
    public const string cGenericMethodParameterArgumentListStart = "{";
    public const string cGenericMethodParameterArgumentListEnd = "}";
}
核心疑问
  1. 如何可靠获取此类嵌套泛型结构体的参数列表?
  2. .NET中是否有标准方法可以根据Type实例生成XML注释文件中对应的成员名?
解决方案

1. 可靠获取嵌套泛型结构体的参数列表

问题根源是Type.GetGenericArguments()会返回所有层级泛型类型的参数:对于嵌套在泛型类中的泛型结构体,它会先返回外层类的泛型参数,再返回结构体自身的泛型参数,因此出现4个参数的异常。

正确逻辑需要区分泛型类型定义和构造泛型类型:

  • 泛型类型定义(如MyStruct<TS1, TS2>本身):直接用Type.GetGenericArguments()即可获取自身的泛型参数。
  • 构造泛型类型(如MyStruct<string, T1>):先通过Type.GetGenericTypeDefinition()获取其原始泛型定义,再从所有参数中截取与该定义匹配的数量的参数。

修改后的参数获取方法:

private static Type[] GetOwnGenericArguments(Type constructedType)
{
    if (!constructedType.IsGenericType)
        return Array.Empty<Type>();
    
    var genericTypeDef = constructedType.GetGenericTypeDefinition();
    var allArgs = constructedType.GetGenericArguments();
    // 只保留当前泛型类型定义对应的参数数量
    return allArgs.Skip(allArgs.Length - genericTypeDef.GetGenericArguments().Length).ToArray();
}

将原方法中的Type[] myArgumentTypes = type.GetGenericArguments();替换为上述方法调用,即可得到MyStruct自身的2个泛型参数,同时避免混入外层Class1的参数。原代码中复杂的外层参数过滤逻辑也可同步移除。

2. .NET中的标准方法生成XML注释成员名

.NET Framework没有公开的官方API直接生成XML注释规范的成员名称,但可以通过以下两种方式实现:

方式一:使用System.Reflection.Metadata(推荐)

在.NET Framework 4.6+或.NET Core/.NET 5+中,可借助System.Reflection.Metadata包读取程序集元数据,生成符合规范的成员名称。示例代码:

using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Reflection;

public static string GetXmlMemberName(MemberInfo member)
{
    using var stream = new FileStream(member.DeclaringType.Assembly.Location, FileMode.Open, FileAccess.Read);
    using var peReader = new PEReader(stream);
    var metadataReader = peReader.GetMetadataReader();
    
    if (member is MethodInfo method)
    {
        var methodDefHandle = (MethodDefinitionHandle)MetadataTokens.EntityHandle(method.MetadataToken);
        var methodDef = metadataReader.GetMethodDefinition(methodDefHandle);
        var fullName = $"{metadataReader.GetString(method.DeclaringType.Namespace)}.{metadataReader.GetString(method.DeclaringType.Name)}`{method.DeclaringType.GetGenericArguments().Length}.{metadataReader.GetString(methodDef.Name)}";
        // 完整实现需补充泛型参数、参数类型的格式化逻辑,此处为简化示例
        return $"M:{fullName}";
    }
    // 可扩展处理类型、属性等其他成员
    return string.Empty;
}

该方式能精准处理嵌套泛型、泛型参数编号等细节,生成符合XML注释规范的字符串。

方式二:复用成熟库的逻辑

DocFX、Sandcastle等文档生成库都包含完整的XML注释成员名生成逻辑,可直接参考其实现,或引入相关库的核心功能。例如Sandcastle的XmlCommentsReflectionImporter类就封装了全场景的成员名称生成逻辑。

若不想引入外部库,也可基于反射自行实现,但需覆盖嵌套泛型、泛型方法、数组类型等所有边缘场景,代码量较大。

内容的提问来源于stack exchange,提问作者Tecman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 17:25:19