英文:
Restrict attribute to only one property per class
问题
你是否有办法确保带有 [AttributeUsage(AttributeTargets.Property)]
标记的属性在每个类中最多只能使用一次?
换句话说,一个特定属性要么没有,要么有一个属性被装饰,绝不会超过一个。这是否可以实现?
英文:
Is there a way of ensuring that an attribute marked with [AttributeUsage(AttributeTargets.Property)]
is used maximum once per class?
In other words, either 0 or 1 properties should be decorated with a particular attribute, never more than 1. Can this be done?
答案1
得分: 1
你可以创建自己的分析器,将其编译成 DLL 并包含在项目中。
让我们从“测试”开始,以确保一切都按您的预期工作:
第二个属性类型 - 不同属性一起工作并编译
属性独立计数 - 如果在类中存在任何一个属性超过一次,则不编译
以下是分析器代码。它遍历 C# 语法,计算带有标记为 AttributeUsageAttribute
并具有标志 AttributeTargets.Property
的属性。
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class PropertyAttributeAnalyzer : DiagnosticAnalyzer
{
private const string Category = "Usage";
private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(
"PA001",
"Multiple Properties with Attribute",
"Multiple properties with the attribute '{0}' are found in the class",
Category,
DiagnosticSeverity.Error,
isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(AnalyzeClassSyntax, SyntaxKind.ClassDeclaration);
}
private static void AnalyzeClassSyntax(SyntaxNodeAnalysisContext context)
{
var classDeclaration = (ClassDeclarationSyntax)context.Node;
var occurrences = new Dictionary<string, int>();
foreach (var property in classDeclaration.Members.OfType<PropertyDeclarationSyntax>())
{
var attributes = property.AttributeLists.SelectMany(list => list.Attributes);
foreach (var attribute in attributes)
{
if (!IsDesiredAttribute(context, classDeclaration, attribute))
continue;
if (occurrences.ContainsKey(attribute.Name.ToString()))
{
occurrences[attribute.Name.ToString()]++;
}
else
{
occurrences[attribute.Name.ToString()] = 1;
}
}
}
foreach (var attribute in occurrences.Where(x => x.Value > 1))
{
var diagnostic = Diagnostic.Create(Rule, classDeclaration.Identifier.GetLocation(), attribute.Key);
context.ReportDiagnostic(diagnostic);
}
}
private static bool IsDesiredAttribute(SyntaxNodeAnalysisContext context, ClassDeclarationSyntax classDeclaration, AttributeSyntax attributeSyntax)
{
var semanticModel = context.SemanticModel;
var attributeSymbol = semanticModel.GetSymbolInfo(attributeSyntax).Symbol;
var attributeType = attributeSymbol?.ContainingType;
if (attributeType is null)
return false;
var attributeUsageAttribute = semanticModel.Compilation.GetTypeByMetadataName(typeof(AttributeUsageAttribute).FullName);
var attributeUsage = attributeType.GetAttributes().FirstOrDefault(attr => attr.AttributeClass.Equals(attributeUsageAttribute));
if (attributeUsage is null)
return false;
var validOnArgument = attributeUsage.ConstructorArguments.FirstOrDefault(arg => arg.Type?.Name == "AttributeTargets");
var validTargets = (AttributeTargets)(int)validOnArgument.Value;
return validTargets.HasFlag(AttributeTargets.Property);
}
}
您必须编译分析器并将其 DLL 链接到项目的 csproj 文件中。
<ItemGroup>
<Analyzer Include="C:\Users\Asus\RiderProjects\TestAnalyzer\Analyzer\bin\Debug\net7.0\Analyzer.dll"/>
</ItemGroup>
最后,我的解决方案结构
英文:
You can create your own analyzer, compile it into dll and include in your project.
Let's start with "tests" to ensure everything works as you expect:
Single property - class compiles
Second property - class does not compile
Second attribute type - different attributes work together and compile
Attributes are counted independently - does not compile if any of those exist more than once in a class
Here is analyzer code. It goes over csharp syntax, counts properties with attributes that are marked with AttributeUsageAttribute
and have flag AttributeTargets.Property
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class PropertyAttributeAnalyzer : DiagnosticAnalyzer
{
private const string Category = "Usage";
private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(
"PA001",
"Multiple Properties with Attribute",
"Multiple properties with the attribute '{0}' are found in the class",
Category,
DiagnosticSeverity.Error,
isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(AnalyzeClassSyntax, SyntaxKind.ClassDeclaration);
}
private static void AnalyzeClassSyntax(SyntaxNodeAnalysisContext context)
{
var classDeclaration = (ClassDeclarationSyntax)context.Node;
var occurrences = new Dictionary<string, int>();
foreach (var property in classDeclaration.Members.OfType<PropertyDeclarationSyntax>())
{
var attributes = property.AttributeLists.SelectMany(list => list.Attributes);
foreach (var attribute in attributes)
{
if (!IsDesiredAttribute(context, classDeclaration, attribute))
continue;
if (occurrences.ContainsKey(attribute.Name.ToString()))
{
occurrences[attribute.Name.ToString()]++;
}
else
{
occurrences[attribute.Name.ToString()] = 1;
}
}
}
foreach (var attribute in occurrences.Where(x => x.Value > 1))
{
var diagnostic = Diagnostic.Create(Rule, classDeclaration.Identifier.GetLocation(), attribute.Key);
context.ReportDiagnostic(diagnostic);
}
}
private static bool IsDesiredAttribute(SyntaxNodeAnalysisContext context, ClassDeclarationSyntax classDeclaration, AttributeSyntax attributeSyntax)
{
var semanticModel = context.SemanticModel;
var attributeSymbol = semanticModel.GetSymbolInfo(attributeSyntax).Symbol;
var attributeType = attributeSymbol?.ContainingType;
if (attributeType is null)
return false;
var attributeUsageAttribute = semanticModel.Compilation.GetTypeByMetadataName(typeof(AttributeUsageAttribute).FullName);
var attributeUsage = attributeType.GetAttributes().FirstOrDefault(attr => attr.AttributeClass.Equals(attributeUsageAttribute));
if (attributeUsage is null)
return false;
var validOnArgument = attributeUsage.ConstructorArguments.FirstOrDefault(arg => arg.Type?.Name == "AttributeTargets");
var validTargets = (AttributeTargets)(int)validOnArgument.Value;
return validTargets.HasFlag(AttributeTargets.Property);
}
}
You have to compile Analyzer and link its dll into your project's csproj file
<ItemGroup>
<Analyzer Include="C:\Users\Asus\RiderProjects\TestAnalyzer\Analyzer\bin\Debug\net7.0\Analyzer.dll"/>
</ItemGroup>
Finally, my solution structure
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论