符号系统最佳实践
📚 文档导航
本文档介绍符号系统的最佳实践、性能优化技巧和实战场景。
📖 文档系列
| 文档 | 内容 | 难度 |
|---|---|---|
| 符号系统基础 | 基础概念、符号层次、快速入门 | 🟢 入门 |
| 符号类型详解 | INamedTypeSymbol、IMethodSymbol、IPropertySymbol 等 | 🟡 中级 |
| 符号操作 | 获取、遍历、查询、比较符号 | 🟡 中级 |
| 高级主题 | 继承、接口、特性、显示格式、文档注释 | 🔴 高级 |
| 最佳实践 ⭐ | 性能优化、实战场景、设计模式 | 🟡 中级 |
性能优化
符号缓存策略
using System.Collections.Concurrent;
using Microsoft.CodeAnalysis;
public class SymbolCache
{
private readonly ConcurrentDictionary<string, INamedTypeSymbol> _typeCache = new();
private readonly Compilation _compilation;
public SymbolCache(Compilation compilation)
{
_compilation = compilation;
}
public INamedTypeSymbol GetOrAddType(string metadataName)
{
return _typeCache.GetOrAdd(metadataName, name =>
{
var type = _compilation.GetTypeByMetadataName(name);
if (type == null)
{
throw new InvalidOperationException($"找不到类型: {name}");
}
return type;
});
}
// 预加载常用类型
public void PreloadCommonTypes()
{
var commonTypes = new[]
{
"System.String",
"System.Int32",
"System.Boolean",
"System.Object",
"System.Collections.Generic.List`1",
"System.Collections.Generic.Dictionary`2",
"System.Linq.Enumerable",
"System.Threading.Tasks.Task",
"System.Threading.Tasks.Task`1"
};
foreach (var typeName in commonTypes)
{
GetOrAddType(typeName);
}
}
}批量符号操作
public class BatchSymbolOperations
{
public Dictionary<INamedTypeSymbol, List<IMethodSymbol>> GetPublicMethodsByType(
IEnumerable<INamedTypeSymbol> types)
{
var result = new Dictionary<INamedTypeSymbol, List<IMethodSymbol>>(
SymbolEqualityComparer.Default);
// 批量处理,避免重复遍历
foreach (var type in types)
{
var publicMethods = type.GetMembers()
.OfType<IMethodSymbol>()
.Where(m => m.DeclaredAccessibility == Accessibility.Public &&
m.MethodKind == MethodKind.Ordinary)
.ToList();
result[type] = publicMethods;
}
return result;
}
public HashSet<INamedTypeSymbol> GetAllReferencedTypes(INamedTypeSymbol type)
{
var referencedTypes = new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);
var visited = new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);
CollectReferencedTypes(type, referencedTypes, visited);
return referencedTypes;
}
private void CollectReferencedTypes(
INamedTypeSymbol type,
HashSet<INamedTypeSymbol> referencedTypes,
HashSet<INamedTypeSymbol> visited)
{
if (!visited.Add(type))
return;
// 基类
if (type.BaseType != null)
{
referencedTypes.Add(type.BaseType);
CollectReferencedTypes(type.BaseType, referencedTypes, visited);
}
// 接口
foreach (var iface in type.Interfaces)
{
referencedTypes.Add(iface);
CollectReferencedTypes(iface, referencedTypes, visited);
}
// 成员类型
foreach (var member in type.GetMembers())
{
ITypeSymbol memberType = null;
if (member is IMethodSymbol method)
{
memberType = method.ReturnType as INamedTypeSymbol;
foreach (var param in method.Parameters)
{
if (param.Type is INamedTypeSymbol paramType)
{
referencedTypes.Add(paramType);
}
}
}
else if (member is IPropertySymbol property)
{
memberType = property.Type as INamedTypeSymbol;
}
else if (member is IFieldSymbol field)
{
memberType = field.Type as INamedTypeSymbol;
}
if (memberType is INamedTypeSymbol namedType)
{
referencedTypes.Add(namedType);
}
}
}
}性能优化流程图
最佳实践 vs 反模式
| 场景 | ✅ 最佳实践 | ❌ 反模式 |
|---|---|---|
| 符号比较 | 使用 SymbolEqualityComparer.Default | 使用 == 或 Equals() |
| 类型查找 | 缓存 GetTypeByMetadataName 结果 | 每次都重新查找 |
| 成员遍历 | 使用 GetMembers() 一次性获取 | 多次调用不同的 Get 方法 |
| 特性检查 | 使用 GetAttributes() | 解析语法节点 |
| 集合存储 | 使用 SymbolEqualityComparer | 使用默认比较器 |
| 符号查询 | 批量处理后过滤 | 逐个查询 |
真实应用场景
场景 1: 生成 Builder 模式代码
public class BuilderGenerator
{
public string GenerateBuilder(INamedTypeSymbol classSymbol)
{
var className = classSymbol.Name;
var builderName = $"{className}Builder";
var sb = new StringBuilder();
sb.AppendLine($"public class {builderName}");
sb.AppendLine("{");
// 为每个公共属性生成字段和方法
var properties = classSymbol.GetMembers()
.OfType<IPropertySymbol>()
.Where(p => p.DeclaredAccessibility == Accessibility.Public &&
p.SetMethod != null);
foreach (var prop in properties)
{
var propType = prop.Type.ToDisplayString();
var propName = prop.Name;
var fieldName = $"_{char.ToLower(propName[0])}{propName.Substring(1)}";
sb.AppendLine($" private {propType} {fieldName};");
}
sb.AppendLine();
foreach (var prop in properties)
{
var propType = prop.Type.ToDisplayString();
var propName = prop.Name;
var fieldName = $"_{char.ToLower(propName[0])}{propName.Substring(1)}";
sb.AppendLine($" public {builderName} With{propName}({propType} value)");
sb.AppendLine($" {{");
sb.AppendLine($" {fieldName} = value;");
sb.AppendLine($" return this;");
sb.AppendLine($" }}");
sb.AppendLine();
}
sb.AppendLine($" public {className} Build()");
sb.AppendLine($" {{");
sb.AppendLine($" return new {className}");
sb.AppendLine($" {{");
foreach (var prop in properties)
{
var propName = prop.Name;
var fieldName = $"_{char.ToLower(propName[0])}{propName.Substring(1)}";
sb.AppendLine($" {propName} = {fieldName},");
}
sb.AppendLine($" }};");
sb.AppendLine($" }}");
sb.AppendLine("}");
return sb.ToString();
}
}场景 2: 验证 API 兼容性
public class APICompatibilityChecker
{
public List<string> CheckCompatibility(
INamedTypeSymbol oldVersion,
INamedTypeSymbol newVersion)
{
var issues = new List<string>();
// 检查公共成员是否被移除
var oldPublicMembers = oldVersion.GetMembers()
.Where(m => m.DeclaredAccessibility == Accessibility.Public)
.ToList();
foreach (var oldMember in oldPublicMembers)
{
var newMember = newVersion.GetMembers(oldMember.Name)
.FirstOrDefault(m => SymbolEqualityComparer.Default.Equals(m, oldMember));
if (newMember == null)
{
issues.Add($"成员 {oldMember.Name} 已被移除");
}
else if (oldMember is IMethodSymbol oldMethod && newMember is IMethodSymbol newMethod)
{
// 检查方法签名是否改变
if (!SymbolEqualityComparer.Default.Equals(oldMethod.ReturnType, newMethod.ReturnType))
{
issues.Add($"方法 {oldMethod.Name} 的返回类型已改变");
}
if (oldMethod.Parameters.Length != newMethod.Parameters.Length)
{
issues.Add($"方法 {oldMethod.Name} 的参数数量已改变");
}
}
}
return issues;
}
}场景 3: 代码度量分析
public class CodeMetrics
{
public class TypeMetrics
{
public string TypeName { get; set; }
public int PublicMethods { get; set; }
public int PrivateMethods { get; set; }
public int Properties { get; set; }
public int Fields { get; set; }
public int LinesOfCode { get; set; }
public int CyclomaticComplexity { get; set; }
}
public TypeMetrics AnalyzeType(INamedTypeSymbol type)
{
var metrics = new TypeMetrics
{
TypeName = type.ToDisplayString()
};
foreach (var member in type.GetMembers())
{
switch (member)
{
case IMethodSymbol method when method.MethodKind == MethodKind.Ordinary:
if (method.DeclaredAccessibility == Accessibility.Public)
metrics.PublicMethods++;
else
metrics.PrivateMethods++;
break;
case IPropertySymbol:
metrics.Properties++;
break;
case IFieldSymbol:
metrics.Fields++;
break;
}
}
return metrics;
}
}场景 4: 依赖注入容器生成
public class DIContainerGenerator
{
public string GenerateRegistrations(IEnumerable<INamedTypeSymbol> types)
{
var sb = new StringBuilder();
sb.AppendLine("public static class ServiceRegistrations");
sb.AppendLine("{");
sb.AppendLine(" public static IServiceCollection AddGeneratedServices(");
sb.AppendLine(" this IServiceCollection services)");
sb.AppendLine(" {");
foreach (var type in types)
{
// 查找构造函数
var constructor = type.Constructors
.Where(c => c.DeclaredAccessibility == Accessibility.Public)
.OrderByDescending(c => c.Parameters.Length)
.FirstOrDefault();
if (constructor == null) continue;
// 查找实现的接口
var serviceInterface = type.Interfaces.FirstOrDefault();
if (serviceInterface != null)
{
sb.AppendLine($" services.AddScoped<{serviceInterface.ToDisplayString()}, {type.ToDisplayString()}>();");
}
else
{
sb.AppendLine($" services.AddScoped<{type.ToDisplayString()}>();");
}
}
sb.AppendLine(" return services;");
sb.AppendLine(" }");
sb.AppendLine("}");
return sb.ToString();
}
}场景 5: API 文档生成
public class APIDocumentationGenerator
{
public string GenerateDocumentation(INamedTypeSymbol type)
{
var sb = new StringBuilder();
// 类型标题
sb.AppendLine($"# {type.Name}");
sb.AppendLine();
// 命名空间
sb.AppendLine($"**命名空间**: `{type.ContainingNamespace.ToDisplayString()}`");
sb.AppendLine();
// 程序集
sb.AppendLine($"**程序集**: {type.ContainingAssembly.Name}");
sb.AppendLine();
// 类型说明
var xmlDoc = type.GetDocumentationCommentXml();
if (!string.IsNullOrEmpty(xmlDoc))
{
var summary = ExtractSummary(xmlDoc);
if (!string.IsNullOrEmpty(summary))
{
sb.AppendLine("## 说明");
sb.AppendLine(summary);
sb.AppendLine();
}
}
// 继承层次
if (type.BaseType != null && type.BaseType.SpecialType != SpecialType.System_Object)
{
sb.AppendLine("## 继承");
sb.AppendLine($"```");
PrintInheritanceChain(type, sb);
sb.AppendLine($"```");
sb.AppendLine();
}
// 实现的接口
if (type.Interfaces.Length > 0)
{
sb.AppendLine("## 实现的接口");
foreach (var iface in type.Interfaces)
{
sb.AppendLine($"- `{iface.ToDisplayString()}`");
}
sb.AppendLine();
}
// 公共方法
var publicMethods = type.GetMembers()
.OfType<IMethodSymbol>()
.Where(m => m.DeclaredAccessibility == Accessibility.Public &&
m.MethodKind == MethodKind.Ordinary);
if (publicMethods.Any())
{
sb.AppendLine("## 公共方法");
foreach (var method in publicMethods)
{
sb.AppendLine($"### {method.Name}");
sb.AppendLine($"```csharp");
sb.AppendLine(method.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat));
sb.AppendLine($"```");
var methodDoc = method.GetDocumentationCommentXml();
if (!string.IsNullOrEmpty(methodDoc))
{
var methodSummary = ExtractSummary(methodDoc);
if (!string.IsNullOrEmpty(methodSummary))
{
sb.AppendLine(methodSummary);
}
}
sb.AppendLine();
}
}
return sb.ToString();
}
private string ExtractSummary(string xml)
{
if (string.IsNullOrEmpty(xml)) return string.Empty;
try
{
var doc = XDocument.Parse(xml);
return doc.Descendants("summary").FirstOrDefault()?.Value.Trim() ?? string.Empty;
}
catch
{
return string.Empty;
}
}
private void PrintInheritanceChain(INamedTypeSymbol type, StringBuilder sb, int depth = 0)
{
var indent = new string(' ', depth * 2);
sb.AppendLine($"{indent}{type.ToDisplayString()}");
if (type.BaseType != null && type.BaseType.SpecialType != SpecialType.System_Object)
{
PrintInheritanceChain(type.BaseType, sb, depth + 1);
}
}
}场景 6: 代码质量分析
public class CodeQualityAnalyzer
{
public class QualityReport
{
public int TotalTypes { get; set; }
public int PublicTypes { get; set; }
public int AbstractTypes { get; set; }
public int SealedTypes { get; set; }
public int InterfaceTypes { get; set; }
public double AverageMethodsPerType { get; set; }
public double AveragePropertiesPerType { get; set; }
public List<string> LargeClasses { get; set; } = new();
public List<string> GodClasses { get; set; } = new();
}
public QualityReport AnalyzeAssembly(IAssemblySymbol assembly)
{
var report = new QualityReport();
var allTypes = GetAllTypes(assembly.GlobalNamespace).ToList();
report.TotalTypes = allTypes.Count;
report.PublicTypes = allTypes.Count(t => t.DeclaredAccessibility == Accessibility.Public);
report.AbstractTypes = allTypes.Count(t => t.IsAbstract);
report.SealedTypes = allTypes.Count(t => t.IsSealed);
report.InterfaceTypes = allTypes.Count(t => t.TypeKind == TypeKind.Interface);
var methodCounts = allTypes.Select(t => t.GetMembers().OfType<IMethodSymbol>().Count()).ToList();
var propertyCounts = allTypes.Select(t => t.GetMembers().OfType<IPropertySymbol>().Count()).ToList();
report.AverageMethodsPerType = methodCounts.Any() ? methodCounts.Average() : 0;
report.AveragePropertiesPerType = propertyCounts.Any() ? propertyCounts.Average() : 0;
// 查找大类(方法数 > 20)
report.LargeClasses = allTypes
.Where(t => t.GetMembers().OfType<IMethodSymbol>().Count() > 20)
.Select(t => t.ToDisplayString())
.ToList();
// 查找上帝类(方法数 > 50 或属性数 > 30)
report.GodClasses = allTypes
.Where(t => t.GetMembers().OfType<IMethodSymbol>().Count() > 50 ||
t.GetMembers().OfType<IPropertySymbol>().Count() > 30)
.Select(t => t.ToDisplayString())
.ToList();
return report;
}
private IEnumerable<INamedTypeSymbol> GetAllTypes(INamespaceSymbol ns)
{
foreach (var type in ns.GetTypeMembers())
{
yield return type;
// 递归获取嵌套类型
foreach (var nestedType in GetNestedTypes(type))
{
yield return nestedType;
}
}
foreach (var childNs in ns.GetNamespaceMembers())
{
foreach (var type in GetAllTypes(childNs))
{
yield return type;
}
}
}
private IEnumerable<INamedTypeSymbol> GetNestedTypes(INamedTypeSymbol type)
{
foreach (var member in type.GetTypeMembers())
{
yield return member;
foreach (var nested in GetNestedTypes(member))
{
yield return nested;
}
}
}
}符号系统的设计模式
访问者模式
使用访问者模式遍历符号树:
public abstract class SymbolVisitor
{
public virtual void Visit(ISymbol symbol)
{
switch (symbol)
{
case INamespaceSymbol ns:
VisitNamespace(ns);
break;
case INamedTypeSymbol type:
VisitNamedType(type);
break;
case IMethodSymbol method:
VisitMethod(method);
break;
case IPropertySymbol property:
VisitProperty(property);
break;
case IFieldSymbol field:
VisitField(field);
break;
}
}
protected virtual void VisitNamespace(INamespaceSymbol symbol)
{
foreach (var member in symbol.GetMembers())
{
Visit(member);
}
}
protected virtual void VisitNamedType(INamedTypeSymbol symbol)
{
foreach (var member in symbol.GetMembers())
{
Visit(member);
}
}
protected virtual void VisitMethod(IMethodSymbol symbol) { }
protected virtual void VisitProperty(IPropertySymbol symbol) { }
protected virtual void VisitField(IFieldSymbol symbol) { }
}
// 使用示例
public class PublicAPICollector : SymbolVisitor
{
public List<ISymbol> PublicAPIs { get; } = new();
protected override void VisitNamedType(INamedTypeSymbol symbol)
{
if (symbol.DeclaredAccessibility == Accessibility.Public)
{
PublicAPIs.Add(symbol);
}
base.VisitNamedType(symbol);
}
protected override void VisitMethod(IMethodSymbol symbol)
{
if (symbol.DeclaredAccessibility == Accessibility.Public)
{
PublicAPIs.Add(symbol);
}
}
}符号的调试技巧
符号信息输出
public class SymbolDebugger
{
public void DumpSymbolInfo(ISymbol symbol, int indentLevel = 0)
{
var indent = new string(' ', indentLevel * 2);
Console.WriteLine($"{indent}符号类型: {symbol.Kind}");
Console.WriteLine($"{indent}名称: {symbol.Name}");
Console.WriteLine($"{indent}完整名称: {symbol.ToDisplayString()}");
Console.WriteLine($"{indent}元数据名称: {symbol.MetadataName}");
Console.WriteLine($"{indent}访问修饰符: {symbol.DeclaredAccessibility}");
Console.WriteLine($"{indent}是静态: {symbol.IsStatic}");
Console.WriteLine($"{indent}是虚拟: {symbol.IsVirtual}");
Console.WriteLine($"{indent}是抽象: {symbol.IsAbstract}");
Console.WriteLine($"{indent}是密封: {symbol.IsSealed}");
Console.WriteLine($"{indent}是外部: {symbol.IsExtern}");
// 包含的符号
if (symbol.ContainingSymbol != null)
{
Console.WriteLine($"{indent}包含在: {symbol.ContainingSymbol.ToDisplayString()}");
}
// 特性
var attributes = symbol.GetAttributes();
if (attributes.Length > 0)
{
Console.WriteLine($"{indent}特性:");
foreach (var attr in attributes)
{
Console.WriteLine($"{indent} - {attr.AttributeClass?.ToDisplayString()}");
}
}
// 类型特定信息
if (symbol is INamedTypeSymbol namedType)
{
DumpNamedTypeInfo(namedType, indentLevel + 1);
}
else if (symbol is IMethodSymbol method)
{
DumpMethodInfo(method, indentLevel + 1);
}
}
private void DumpNamedTypeInfo(INamedTypeSymbol type, int indentLevel)
{
var indent = new string(' ', indentLevel * 2);
Console.WriteLine($"{indent}类型种类: {type.TypeKind}");
Console.WriteLine($"{indent}是泛型: {type.IsGenericType}");
Console.WriteLine($"{indent}成员数量: {type.GetMembers().Length}");
if (type.BaseType != null)
{
Console.WriteLine($"{indent}基类: {type.BaseType.ToDisplayString()}");
}
if (type.Interfaces.Length > 0)
{
Console.WriteLine($"{indent}接口:");
foreach (var iface in type.Interfaces)
{
Console.WriteLine($"{indent} - {iface.ToDisplayString()}");
}
}
}
private void DumpMethodInfo(IMethodSymbol method, int indentLevel)
{
var indent = new string(' ', indentLevel * 2);
Console.WriteLine($"{indent}方法种类: {method.MethodKind}");
Console.WriteLine($"{indent}返回类型: {method.ReturnType.ToDisplayString()}");
Console.WriteLine($"{indent}是异步: {method.IsAsync}");
Console.WriteLine($"{indent}是扩展方法: {method.IsExtensionMethod}");
Console.WriteLine($"{indent}参数数量: {method.Parameters.Length}");
if (method.Parameters.Length > 0)
{
Console.WriteLine($"{indent}参数:");
foreach (var param in method.Parameters)
{
Console.WriteLine($"{indent} - {param.Type.ToDisplayString()} {param.Name}");
}
}
}
}常见问题解答
Q1: 如何比较两个符号是否相同?
A: 使用 SymbolEqualityComparer.Default.Equals(symbol1, symbol2)。永远不要使用 == 或 Equals() 方法,因为它们比较的是引用而不是语义相等性。
Q2: 如何获取类的所有成员(包括继承的)?
A: 使用 GetMembers() 获取所有成员,包括继承的成员。如果只想获取当前类型声明的成员,可以检查 member.ContainingType 是否等于当前类型。
Q3: 如何检查类型是否实现了特定接口?
A: 使用 typeSymbol.AllInterfaces.Any(i => i.Name == "IMyInterface")。注意使用 AllInterfaces 而不是 Interfaces,因为前者包含所有继承的接口。
Q4: 如何获取泛型类型的类型参数?
A: 对于泛型类型定义,使用 typeSymbol.TypeParameters 获取类型参数。对于构造的泛型类型,使用 typeSymbol.TypeArguments 获取具体的类型实参。例如,List<T> 的 TypeParameters 是 T,而 List<int> 的 TypeArguments 是 int。
Q5: 符号的生命周期是怎样的?
A: 符号的生命周期与 Compilation 对象绑定。当 Compilation 被释放时,所有相关的符号也会失效。因此,不要在不同的 Compilation 之间共享符号引用。如果需要跨 Compilation 使用符号信息,应该保存符号的元数据名称或完整限定名称,然后在新的 Compilation 中重新查找。
Q6: 如何获取符号的定义位置?
A: 使用 symbol.Locations.FirstOrDefault() 获取符号的定义位置。可以通过 location.SourceTree?.FilePath 获取文件路径。
Q7: 如何检查方法是否重写了基类方法?
A: 使用 method.IsOverride 检查是否是重写方法,使用 method.OverriddenMethod 获取被重写的基类方法。
Q8: 如何查找所有带特定特性的类?
A: 遍历命名空间,检查每个类型的特性:
var classesWithAttribute = namespace.GetMembers()
.OfType<INamedTypeSymbol>()
.Where(t => t.GetAttributes()
.Any(a => a.AttributeClass?.Name == "MyAttribute"));Q9: 如何获取符号的 XML 文档注释?
A: 使用 symbol.GetDocumentationCommentXml() 获取 XML 格式的文档注释,然后解析 XML 获取具体内容。
Q10: 如何检查类型是否可以为 null?
A: 检查类型是引用类型还是可空值类型:
bool canBeNull = type.IsReferenceType ||
(type is INamedTypeSymbol namedType &&
namedType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T);下一步学习
完成符号系统的学习后,建议继续学习以下内容:
- 语义模型 API - 深入了解如何使用语义模型获取符号信息
- 类型系统深入 - 学习复杂类型的处理,包括泛型、可空类型等
- 编译 API - 了解如何创建和管理编译单元
- 代码生成 API - 学习如何使用符号信息生成代码
- 增量生成器管道 - 了解如何在增量生成器中高效使用符号
🔗 相关资源
深入学习
API 参考
实战示例
- Builder 生成器 - 使用符号生成 Builder 模式代码
- 增量生成器示例 - 在增量生成器中使用符号
最后更新: 2025-01-21