如何在.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,可直接解析代码语法树,精准定位目标类型的属性引用,适合单元测试场景(比如验证映射逻辑的属性使用正确性)。
实现步骤
- 安装NuGet包
Microsoft.CodeAnalysis.CSharp(.NET7对应4.x版本即可)。 - 编写扩展方法解析源代码:
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
相关产品推荐
相关产品推荐

