如何在C#中实现检查集合唯一性的自定义特性?
在C#中实现集合唯一性检查的自定义特性
要实现你想要的效果,C#原生自定义特性仅作为元数据标记,无法直接触发编译时检查——你需要借助Roslyn静态代码分析器来扫描带特性的属性,验证初始化集合的唯一性。下面是具体实现步骤:
1. 定义标记用的自定义特性
先创建一个特性类,指定它可应用在属性上:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class HasUniqueItemsAttribute : Attribute { }
2. 编写Roslyn编译时分析器
Roslyn分析器能在编译阶段扫描代码,识别带HasUniqueItemsAttribute的属性,并检查其初始化集合的元素是否重复。核心逻辑如下:
- 筛选出所有标记了目标特性的属性声明
- 解析属性的初始化表达式,判断是否为集合初始化器
- 遍历初始化元素,检查是否存在重复值
- 发现重复时抛出编译错误
以下是简化版的分析器代码:
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Immutable; using System.Linq; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class HasUniqueItemsAnalyzer : DiagnosticAnalyzer { public const string DiagnosticId = "HasUniqueItems001"; private static readonly LocalizableString Title = "集合包含重复元素"; private static readonly LocalizableString MessageFormat = "属性 '{0}' 的初始化集合存在重复元素: {1}"; private static readonly LocalizableString Description = "标记了HasUniqueItemsAttribute的集合必须包含唯一元素"; private const string Category = "Usage"; private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); context.RegisterSymbolAction(AnalyzeProperty, SymbolKind.Property); } private void AnalyzeProperty(SymbolAnalysisContext context) { var propertySymbol = (IPropertySymbol)context.Symbol; // 检查属性是否标记了目标特性 if (!propertySymbol.GetAttributes().Any(attr => attr.AttributeClass.Name == nameof(HasUniqueItemsAttribute))) return; // 获取属性声明的语法节点 var propertyDeclaration = propertySymbol.DeclaringSyntaxReferences.First().GetSyntax() as PropertyDeclarationSyntax; var initializer = propertyDeclaration?.Initializer; if (initializer == null) return; // 判断是否为集合初始化器 var collectionInitializer = initializer.Value as CollectionInitializerExpressionSyntax; if (collectionInitializer == null) return; // 提取初始化元素的文本值(示例针对字符串,可扩展至其他类型) var elements = collectionInitializer.Initializers .Select(expr => expr.Expression as LiteralExpressionSyntax) .Where(lit => lit != null) .Select(lit => lit.Token.ValueText) .ToList(); // 检查重复元素 var duplicates = elements.GroupBy(e => e) .Where(g => g.Count() > 1) .Select(g => g.Key) .ToList(); foreach (var duplicate in duplicates) { var diagnostic = Diagnostic.Create(Rule, initializer.GetLocation(), propertySymbol.Name, duplicate); context.ReportDiagnostic(diagnostic); } } }
3. 应用分析器
- 将分析器代码打包为NuGet包,或直接添加到项目中
- 当你编写带
HasUniqueItemsAttribute的属性并初始化重复元素时,编译器会直接抛出错误,实现"运行前提示"的效果
备选:运行时检查(非编译时)
如果暂时不想编写Roslyn分析器,可在属性setter中加入运行时检查,但仅能在程序运行时发现问题:
[HasUniqueItemsAttribute] private IList<string> _myCollection; public IList<string> MyCollection { get => _myCollection; set { if (value.Distinct().Count() != value.Count) throw new ArgumentException("集合包含重复元素"); _myCollection = value; } }
内容的提问来源于stack exchange,提问作者user20291437
相关产品推荐
相关产品推荐

