特性处理
深入理解 Roslyn 中的特性(Attribute)处理
📋 文档信息
| 属性 | 值 |
|---|---|
| 难度 | 中级 |
| 阅读时间 | 20 分钟 |
| 前置知识 | C# 特性、元数据 |
| 相关文档 | ISymbol 基础、INamedTypeSymbol |
🎯 学习目标
完成本文档后,你将能够:
- ✅ 理解 AttributeData 的结构和用法
- ✅ 提取特性的构造函数参数
- ✅ 处理特性的命名参数
- ✅ 处理不同类型的特性参数值
- ✅ 在源生成器中使用特性驱动代码生成
📚 快速导航
| 主题 | 说明 |
|---|---|
| AttributeData 基础 | 特性数据的基本结构 |
| 构造函数参数 | 提取构造函数参数 |
| 命名参数 | 处理命名参数 |
| 参数值类型 | 处理不同类型的参数值 |
| 完整示例 | 实用的特性分析器 |
| 实际应用 | 特性驱动的代码生成 |
AttributeData 基础
特性(Attribute)是 C# 中的元数据,Roslyn 使用 AttributeData 类来表示特性。
获取特性
csharp
// 获取符号的所有特性
ISymbol symbol;
ImmutableArray<AttributeData> attributes = symbol.GetAttributes();
foreach (var attribute in attributes)
{
// ============================================================
// 特性类型
// ============================================================
INamedTypeSymbol attributeClass = attribute.AttributeClass;
string attributeName = attributeClass?.Name;
string fullName = attributeClass?.ToDisplayString();
// ============================================================
// 特性位置
// ============================================================
// 特性应用的位置
Location location = attribute.ApplicationSyntaxReference?.GetSyntax().GetLocation();
}特性检查
csharp
/// <summary>
/// 特性检查工具
/// </summary>
public class AttributeChecker
{
/// <summary>
/// 检查符号是否有特定特性
/// </summary>
public bool HasAttribute(ISymbol symbol, string attributeName)
{
return symbol.GetAttributes().Any(a =>
a.AttributeClass?.Name == attributeName ||
a.AttributeClass?.Name == attributeName + "Attribute");
}
/// <summary>
/// 获取特定特性
/// </summary>
public AttributeData GetAttribute(ISymbol symbol, string attributeName)
{
return symbol.GetAttributes().FirstOrDefault(a =>
a.AttributeClass?.Name == attributeName ||
a.AttributeClass?.Name == attributeName + "Attribute");
}
/// <summary>
/// 获取所有特定类型的特性
/// </summary>
public IEnumerable<AttributeData> GetAttributes(
ISymbol symbol,
string attributeName)
{
return symbol.GetAttributes().Where(a =>
a.AttributeClass?.Name == attributeName ||
a.AttributeClass?.Name == attributeName + "Attribute");
}
/// <summary>
/// 检查特性是否继承自指定基类
/// </summary>
public bool IsAttributeDerivedFrom(
AttributeData attribute,
string baseAttributeName)
{
var current = attribute.AttributeClass;
while (current != null)
{
if (current.Name == baseAttributeName ||
current.Name == baseAttributeName + "Attribute")
{
return true;
}
current = current.BaseType;
}
return false;
}
}构造函数参数
特性的构造函数参数通过 ConstructorArguments 属性访问。
提取构造函数参数
csharp
AttributeData attribute;
// 获取构造函数参数
ImmutableArray<TypedConstant> constructorArguments =
attribute.ConstructorArguments;
foreach (var arg in constructorArguments)
{
// 参数类型
TypedConstantKind kind = arg.Kind;
// 参数的类型符号
ITypeSymbol type = arg.Type;
// 参数值
object value = arg.Value;
// 处理不同类型的参数
switch (kind)
{
case TypedConstantKind.Primitive:
// 基本类型:int, string, bool 等
Console.WriteLine($"Primitive: {value}");
break;
case TypedConstantKind.Enum:
// 枚举值
Console.WriteLine($"Enum: {value}");
break;
case TypedConstantKind.Type:
// typeof(T)
var typeValue = (ITypeSymbol)value;
Console.WriteLine($"Type: {typeValue.ToDisplayString()}");
break;
case TypedConstantKind.Array:
// 数组
var arrayValues = arg.Values;
Console.WriteLine($"Array: [{string.Join(", ", arrayValues)}]");
break;
}
}构造函数参数处理器
csharp
/// <summary>
/// 构造函数参数处理器
/// </summary>
public class ConstructorArgumentHandler
{
/// <summary>
/// 提取所有构造函数参数
/// </summary>
public List<object> GetConstructorArguments(AttributeData attribute)
{
return attribute.ConstructorArguments
.Select(arg => ExtractValue(arg))
.ToList();
}
/// <summary>
/// 获取指定位置的构造函数参数
/// </summary>
public object GetConstructorArgument(AttributeData attribute, int index)
{
if (index < 0 || index >= attribute.ConstructorArguments.Length)
return null;
return ExtractValue(attribute.ConstructorArguments[index]);
}
/// <summary>
/// 从 TypedConstant 提取值
/// </summary>
private object ExtractValue(TypedConstant constant)
{
switch (constant.Kind)
{
case TypedConstantKind.Primitive:
case TypedConstantKind.Enum:
return constant.Value;
case TypedConstantKind.Type:
return ((ITypeSymbol)constant.Value).ToDisplayString();
case TypedConstantKind.Array:
return constant.Values
.Select(v => ExtractValue(v))
.ToArray();
default:
return constant.Value;
}
}
}命名参数
特性的命名参数(属性和字段赋值)通过 NamedArguments 属性访问。
提取命名参数
csharp
AttributeData attribute;
// 获取命名参数
ImmutableArray<KeyValuePair<string, TypedConstant>> namedArguments =
attribute.NamedArguments;
foreach (var namedArg in namedArguments)
{
// 参数名称
string name = namedArg.Key;
// 参数值
TypedConstant value = namedArg.Value;
Console.WriteLine($"{name} = {value.Value}");
}命名参数处理器
csharp
/// <summary>
/// 命名参数处理器
/// </summary>
public class NamedArgumentHandler
{
/// <summary>
/// 提取所有命名参数
/// </summary>
public Dictionary<string, object> GetNamedArguments(AttributeData attribute)
{
return attribute.NamedArguments
.ToDictionary(
kvp => kvp.Key,
kvp => ExtractValue(kvp.Value));
}
/// <summary>
/// 获取指定名称的命名参数
/// </summary>
public object GetNamedArgument(AttributeData attribute, string name)
{
var namedArg = attribute.NamedArguments
.FirstOrDefault(kvp => kvp.Key == name);
if (namedArg.Key == null)
return null;
return ExtractValue(namedArg.Value);
}
/// <summary>
/// 检查是否有指定的命名参数
/// </summary>
public bool HasNamedArgument(AttributeData attribute, string name)
{
return attribute.NamedArguments.Any(kvp => kvp.Key == name);
}
/// <summary>
/// 从 TypedConstant 提取值
/// </summary>
private object ExtractValue(TypedConstant constant)
{
switch (constant.Kind)
{
case TypedConstantKind.Primitive:
case TypedConstantKind.Enum:
return constant.Value;
case TypedConstantKind.Type:
return ((ITypeSymbol)constant.Value).ToDisplayString();
case TypedConstantKind.Array:
return constant.Values
.Select(v => ExtractValue(v))
.ToArray();
default:
return constant.Value;
}
}
}参数值类型
特性参数可以是多种类型,需要根据 TypedConstantKind 进行处理。
TypedConstantKind 枚举
csharp
public enum TypedConstantKind
{
Error, // 错误
Primitive, // 基本类型(int, string, bool 等)
Enum, // 枚举
Type, // typeof(T)
Array // 数组
}参数值提取器
csharp
/// <summary>
/// 参数值提取器
/// </summary>
public class AttributeValueExtractor
{
/// <summary>
/// 提取基本类型值
/// </summary>
public T GetPrimitiveValue<T>(TypedConstant constant)
{
if (constant.Kind != TypedConstantKind.Primitive)
throw new InvalidOperationException("Not a primitive value");
return (T)constant.Value;
}
/// <summary>
/// 提取枚举值
/// </summary>
public T GetEnumValue<T>(TypedConstant constant) where T : Enum
{
if (constant.Kind != TypedConstantKind.Enum)
throw new InvalidOperationException("Not an enum value");
return (T)constant.Value;
}
/// <summary>
/// 提取类型值
/// </summary>
public ITypeSymbol GetTypeValue(TypedConstant constant)
{
if (constant.Kind != TypedConstantKind.Type)
throw new InvalidOperationException("Not a type value");
return (ITypeSymbol)constant.Value;
}
/// <summary>
/// 提取数组值
/// </summary>
public List<object> GetArrayValue(TypedConstant constant)
{
if (constant.Kind != TypedConstantKind.Array)
throw new InvalidOperationException("Not an array value");
return constant.Values
.Select(v => ExtractValue(v))
.ToList();
}
/// <summary>
/// 通用值提取
/// </summary>
public object ExtractValue(TypedConstant constant)
{
switch (constant.Kind)
{
case TypedConstantKind.Primitive:
case TypedConstantKind.Enum:
return constant.Value;
case TypedConstantKind.Type:
return ((ITypeSymbol)constant.Value).ToDisplayString();
case TypedConstantKind.Array:
return constant.Values
.Select(v => ExtractValue(v))
.ToArray();
case TypedConstantKind.Error:
return null;
default:
return constant.Value;
}
}
/// <summary>
/// 格式化值为代码字符串
/// </summary>
public string FormatValue(TypedConstant constant)
{
switch (constant.Kind)
{
case TypedConstantKind.Primitive:
return FormatPrimitive(constant.Value);
case TypedConstantKind.Enum:
return $"{constant.Type.ToDisplayString()}.{constant.Value}";
case TypedConstantKind.Type:
var typeSymbol = (ITypeSymbol)constant.Value;
return $"typeof({typeSymbol.ToDisplayString()})";
case TypedConstantKind.Array:
var elements = constant.Values
.Select(v => FormatValue(v));
return $"new[] {{ {string.Join(", ", elements)} }}";
default:
return "null";
}
}
private string FormatPrimitive(object value)
{
if (value == null) return "null";
if (value is string s) return $"\"{s}\"";
if (value is char c) return $"'{c}'";
if (value is bool b) return b ? "true" : "false";
return value.ToString();
}
}完整示例:特性分析器
csharp
/// <summary>
/// 完整的特性分析器
/// </summary>
public class AttributeAnalyzer
{
/// <summary>
/// 分析特性的完整信息
/// </summary>
public AttributeInfo AnalyzeAttribute(AttributeData attribute)
{
var info = new AttributeInfo
{
AttributeName = attribute.AttributeClass?.Name,
FullName = attribute.AttributeClass?.ToDisplayString()
};
// 构造函数参数
info.ConstructorArguments = attribute.ConstructorArguments
.Select((arg, index) => new ArgumentInfo
{
Index = index,
Type = arg.Type?.ToDisplayString(),
Kind = arg.Kind.ToString(),
Value = ExtractValue(arg)
})
.ToList();
// 命名参数
info.NamedArguments = attribute.NamedArguments
.Select(kvp => new NamedArgumentInfo
{
Name = kvp.Key,
Type = kvp.Value.Type?.ToDisplayString(),
Kind = kvp.Value.Kind.ToString(),
Value = ExtractValue(kvp.Value)
})
.ToList();
return info;
}
/// <summary>
/// 从 TypedConstant 提取值
/// </summary>
private object ExtractValue(TypedConstant constant)
{
switch (constant.Kind)
{
case TypedConstantKind.Primitive:
case TypedConstantKind.Enum:
return constant.Value;
case TypedConstantKind.Type:
return ((ITypeSymbol)constant.Value).ToDisplayString();
case TypedConstantKind.Array:
return constant.Values
.Select(v => ExtractValue(v))
.ToArray();
default:
return constant.Value;
}
}
}
public class AttributeInfo
{
public string AttributeName { get; set; }
public string FullName { get; set; }
public List<ArgumentInfo> ConstructorArguments { get; set; }
public List<NamedArgumentInfo> NamedArguments { get; set; }
}
public class ArgumentInfo
{
public int Index { get; set; }
public string Type { get; set; }
public string Kind { get; set; }
public object Value { get; set; }
}
public class NamedArgumentInfo
{
public string Name { get; set; }
public string Type { get; set; }
public string Kind { get; set; }
public object Value { get; set; }
}实际应用场景
场景 1: 解析自定义特性
csharp
/// <summary>
/// 示例:解析 [GenerateToString] 特性
/// </summary>
public class ToStringAttributeParser
{
public ToStringOptions ParseAttribute(INamedTypeSymbol typeSymbol)
{
// 查找特性
var attribute = typeSymbol.GetAttributes()
.FirstOrDefault(a => a.AttributeClass?.Name == "GenerateToStringAttribute");
if (attribute == null)
return null;
var options = new ToStringOptions();
// 构造函数参数
if (attribute.ConstructorArguments.Length > 0)
{
options.IncludeFieldNames = (bool)attribute.ConstructorArguments[0].Value;
}
// 命名参数
foreach (var namedArg in attribute.NamedArguments)
{
switch (namedArg.Key)
{
case "Format":
options.Format = (string)namedArg.Value.Value;
break;
case "IncludePrivateFields":
options.IncludePrivateFields = (bool)namedArg.Value.Value;
break;
case "ExcludedProperties":
if (namedArg.Value.Kind == TypedConstantKind.Array)
{
options.ExcludedProperties = namedArg.Value.Values
.Select(v => (string)v.Value)
.ToList();
}
break;
}
}
return options;
}
}
public class ToStringOptions
{
public bool IncludeFieldNames { get; set; }
public string Format { get; set; }
public bool IncludePrivateFields { get; set; }
public List<string> ExcludedProperties { get; set; } = new();
}场景 2: 验证特性参数
csharp
/// <summary>
/// 示例:验证特性参数的有效性
/// </summary>
public class AttributeValidator
{
/// <summary>
/// 验证 [Range] 特性
/// </summary>
public ValidationResult ValidateRangeAttribute(AttributeData attribute)
{
var result = new ValidationResult { IsValid = true };
// 检查构造函数参数数量
if (attribute.ConstructorArguments.Length < 2)
{
result.IsValid = false;
result.ErrorMessage = "Range attribute requires min and max values";
return result;
}
// 提取 min 和 max
var min = attribute.ConstructorArguments[0].Value;
var max = attribute.ConstructorArguments[1].Value;
// 验证类型
if (min == null || max == null)
{
result.IsValid = false;
result.ErrorMessage = "Range values cannot be null";
return result;
}
// 验证范围
if (min is IComparable minComparable &&
max is IComparable maxComparable)
{
if (minComparable.CompareTo(maxComparable) > 0)
{
result.IsValid = false;
result.ErrorMessage = "Min value must be less than or equal to max value";
}
}
return result;
}
}
public class ValidationResult
{
public bool IsValid { get; set; }
public string ErrorMessage { get; set; }
}最佳实践
✅ 推荐做法
csharp
/// <summary>
/// 特性处理的最佳实践
/// </summary>
public class AttributeBestPractices
{
// ✅ 1. 检查特性是否存在
public void SafeAttributeAccess(ISymbol symbol)
{
// ✅ 正确:先检查是否存在
var attribute = symbol.GetAttributes()
.FirstOrDefault(a => a.AttributeClass?.Name == "MyAttribute");
if (attribute != null)
{
// 处理特性
}
}
// ✅ 2. 处理特性名称的两种形式
public bool HasAttribute(ISymbol symbol, string name)
{
// ✅ 正确:同时检查带和不带 "Attribute" 后缀
return symbol.GetAttributes().Any(a =>
a.AttributeClass?.Name == name ||
a.AttributeClass?.Name == name + "Attribute");
}
// ✅ 3. 安全提取参数值
public object SafeGetArgument(AttributeData attribute, int index)
{
// ✅ 正确:检查索引范围
if (index < 0 || index >= attribute.ConstructorArguments.Length)
return null;
var arg = attribute.ConstructorArguments[index];
return ExtractValue(arg);
}
// ✅ 4. 处理不同类型的参数值
private object ExtractValue(TypedConstant constant)
{
switch (constant.Kind)
{
case TypedConstantKind.Primitive:
case TypedConstantKind.Enum:
return constant.Value;
case TypedConstantKind.Type:
return ((ITypeSymbol)constant.Value).ToDisplayString();
case TypedConstantKind.Array:
return constant.Values
.Select(v => ExtractValue(v))
.ToArray();
default:
return constant.Value;
}
}
}❌ 应避免的做法
csharp
/// <summary>
/// 应该避免的反模式
/// </summary>
public class AttributeAntiPatterns
{
// ❌ 1. 不检查特性是否存在
public void WrongAttributeAccess(ISymbol symbol)
{
// ❌ 错误:可能返回 null
var attribute = symbol.GetAttributes().First();
// ✅ 正确
// var attribute = symbol.GetAttributes().FirstOrDefault();
// if (attribute != null) { ... }
}
// ❌ 2. 不检查参数索引
public void WrongArgumentAccess(AttributeData attribute)
{
// ❌ 错误:可能越界
var value = attribute.ConstructorArguments[0].Value;
// ✅ 正确
// if (attribute.ConstructorArguments.Length > 0)
// {
// var value = attribute.ConstructorArguments[0].Value;
// }
}
// ❌ 3. 忽略参数类型
public void WrongValueExtraction(TypedConstant constant)
{
// ❌ 错误:直接使用 Value,可能类型不匹配
var value = (string)constant.Value;
// ✅ 正确:检查 Kind
// if (constant.Kind == TypedConstantKind.Primitive &&
// constant.Value is string s)
// {
// // 使用 s
// }
}
}🔑 关键要点
- 特性检查: 使用
GetAttributes()获取符号的所有特性 - 名称匹配: 同时检查带和不带 "Attribute" 后缀的名称
- 参数提取: 区分构造函数参数和命名参数
- 类型处理: 根据
TypedConstantKind处理不同类型的参数值 - 空值检查: 始终检查特性和参数是否存在
📖 相关文档
- 返回索引 - 语义模型 API 概览
- INamedTypeSymbol - 类型符号详解
- 符号比较 - 符号比较和等价性
🚀 下一步
最后更新: 2026-02-05