Skip to content

特性处理

深入理解 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
        // }
    }
}

🔑 关键要点

  1. 特性检查: 使用 GetAttributes() 获取符号的所有特性
  2. 名称匹配: 同时检查带和不带 "Attribute" 后缀的名称
  3. 参数提取: 区分构造函数参数和命名参数
  4. 类型处理: 根据 TypedConstantKind 处理不同类型的参数值
  5. 空值检查: 始终检查特性和参数是否存在

📖 相关文档


🚀 下一步


最后更新: 2026-02-05

基于 MIT 许可发布