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

如何在.NET7(C#11)中程序化识别MyDto的属性引用?

需求背景

给定如下C#代码:

public class MyDto
{
    public string PropertyA { get; }

    public int PropertyB { get; }

    public bool PropertyC { get; }
}

public class MyMapper
{
    public MyResult Map(MyDto objectToMap) => 
        new MyResult { Bla = objectToMap.PropertyA, Blub = objectToMap.PropertyC };
}

可以看到MyMapper仅使用了MyDto的PropertyA和PropertyC,未使用PropertyB。需要实现一个扩展方法FindAllPropertyReferences,达到如下效果:

var namesOfUsedProperties = typeof(MyDto).FindAllPropertyReferences();
namesOfUsedProperties.Should().Contain("PropertyA", "PropertyC");
namesOfUsedProperties.ShouldNot().Contain("PropertyB");

环境为C#11和.NET7,以下是两种可行实现方案:


方案一:基于Roslyn源代码分析

Roslyn是官方编译器API,可直接解析代码语法树,精准定位目标类型的属性引用,适合单元测试场景(比如验证映射逻辑的属性使用正确性)。

实现步骤

  1. 安装NuGet包Microsoft.CodeAnalysis.CSharp(.NET7对应4.x版本即可)。
  2. 编写扩展方法解析源代码:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
using System.IO;
using System.Linq;

public static class TypeExtensions
{
    public static IEnumerable<string> FindAllPropertyReferences(this Type targetType, params string[] sourceFilePaths)
    {
        var usedProperties = new HashSet<string>();

        foreach (var filePath in sourceFilePaths)
        {
            var sourceText = File.ReadAllText(filePath);
            var syntaxTree = CSharpSyntaxTree.ParseText(sourceText);
            var semanticModel = syntaxTree.GetSemanticModel();
            var root = syntaxTree.GetRoot();

            // 遍历所有成员访问表达式
            var memberAccesses = root.DescendantNodes().OfType<MemberAccessExpressionSyntax>();
            foreach (var access in memberAccesses)
            {
                var exprType = semanticModel.GetTypeInfo(access.Expression).Type;
                // 匹配目标类型的属性引用
                if (exprType?.FullName == targetType.FullName)
                {
                    usedProperties.Add(access.Name.Identifier.Text);
                }
            }
        }

        return usedProperties;
    }
}

使用示例

传入包含MyMapper的源代码文件路径即可:

var usedProps = typeof(MyDto).FindAllPropertyReferences(@"C:\Projects\YourApp\MyMapper.cs");

方案二:基于IL字节码分析

无需依赖源代码,直接分析已编译程序集的IL指令,找到目标类型属性的getter调用,适合运行时检查场景。

实现代码

using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

public static class TypeExtensions
{
    public static IEnumerable<string> FindAllPropertyReferences(this Type targetType)
    {
        var assembly = targetType.Assembly;
        var usedProperties = new HashSet<string>();
        // 构建目标类型属性getter的元数据令牌与属性名映射
        var targetGetters = targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                                      .ToDictionary(p => p.GetGetMethod(true)?.MetadataToken, p => p.Name);

        // 遍历程序集中所有类型的所有方法
        foreach (var type in assembly.GetTypes())
        {
            foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
            {
                if (!method.HasMethodBody) continue;

                var ilBytes = method.GetMethodBody().GetILAsByteArray();
                int offset = 0;

                while (offset < ilBytes.Length)
                {
                    var opCode = (OpCode)ilBytes[offset];
                    offset++;

                    // 处理调用属性getter的Call/Callvirt指令
                    if (opCode is OpCodes.Call or OpCodes.Callvirt)
                    {
                        // 读取元数据令牌(4字节)
                        var token = ilBytes[offset] | (ilBytes[offset + 1] << 8) | (ilBytes[offset + 2] << 16) | (ilBytes[offset + 3] << 24);
                        offset += 4;

                        if (targetGetters.TryGetValue(token, out var propName))
                        {
                            usedProperties.Add(propName);
                        }
                    }
                    // 跳过其他指令的操作数
                    else if (opCode.OperandType != OperandType.InlineNone)
                    {
                        offset += GetOperandSize(opCode);
                    }
                }
            }
        }

        return usedProperties;
    }

    private static int GetOperandSize(OpCode opCode)
    {
        return opCode.OperandType switch
        {
            OperandType.InlineI or OperandType.InlineField or OperandType.InlineMethod => 4,
            OperandType.InlineI8 or OperandType.InlineR => 8,
            OperandType.ShortInlineI or OperandType.ShortInlineVar => 1,
            OperandType.ShortInlineR => 4,
            _ => 0
        };
    }
}

使用示例

直接调用扩展方法即可:

var usedProps = typeof(MyDto).FindAllPropertyReferences();

注意事项

  • Roslyn方案需要源代码,能捕获所有显式的属性引用,但无法处理反射调用的情况。
  • IL分析方案基于编译后的程序集,无需源代码,但同样无法捕获反射调用,且需注意IL指令偏移计算的准确性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 04:37:34