C# 强制执行特定数量的参数
我有一个记录方法名称(通过反射获得)和参数(手动传递给记录器)的记录器。下面是一个正确记录日志的示例:C# 强制执行特定数量的参数,c#,logging,C#,Logging,我有一个记录方法名称(通过反射获得)和参数(手动传递给记录器)的记录器。下面是一个正确记录日志的示例: public void Foo() { // This is correct - the method has no parameters, neither does the logging Logger.Log(); // Method } public void Foo1(int a, int b) { // Log both of the parameters
public void Foo() {
// This is correct - the method has no parameters, neither does the logging
Logger.Log();
// Method
}
public void Foo1(int a, int b) {
// Log both of the parameters correctly
Logger.Log(a, b);
// Rest of method
}
然而,人们会周期性地错误地称之为这一点。例如:
public void Foo1(int a, int b) {
// Didn't record any of the parameters
Logger.Log();
// Rest of method
}
或
日志方法的签名为:
public void Log(params object[] parameters)
我想通过某种方式要求Logger.Log
具有与调用它的方法相同数量的参数
我知道如何在运行时执行此操作(只需使用反射来获取调用方的参数列表,并将其与我实际接收到的参数进行比较),但我认为这是一个非常糟糕的解决方案,因为绝大多数检查都是不必要的。(这也意味着,只有在运行时您才知道您编写的方法不正确,并且只有在您碰巧执行了该特定方法时才知道)
不幸的是,现在我们没有使用FxCop(或者我只是写了一些规则),我怀疑我无法成功地改变这一事实。除了编写某种类型的编译器插件外,有没有办法强迫人们正确使用这种方法?您应该能够使用新的Roslyn API来完成这项工作。您需要在此处安装SDK: 安装后,您应该转到新项目并导航到可扩展性,您将看到带有代码修复(NuGet+VSIX)模板的项目类型分析器。我创建了一个示例项目,用于显示编译器错误:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AnalyzerTest
{
public static class Logger
{
public static void Log(params object[] parameters)
{
}
}
}
namespace AnalyzerTest
{
public class Foo
{
public void Foo1(int a, int b)
{
// Didn't record any of the parameters
Logger.Log();
// Rest of method
}
}
}
我为analyzer创建了一个单独的项目,下面是analyzer类的代码:
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Semantics;
namespace Analyzer1
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class LoggerAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "Logging";
internal const string Title = "Logging error";
internal const string MessageFormat = "Logging error {0}";
internal const string Description = "You should have the same amount of arguments in the logger as you do in the method.";
internal const string Category = "Syntax";
internal static DiagnosticDescriptor Rule =
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat,
Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description);
public override ImmutableArray<DiagnosticDescriptor>
SupportedDiagnostics
{ get { return ImmutableArray.Create(Rule); } }
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(
AnalyzeNode, SyntaxKind.InvocationExpression);
}
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var invocationExpr = (InvocationExpressionSyntax)context.Node;
var memberAccessExpr = invocationExpr.Expression as MemberAccessExpressionSyntax;
if (memberAccessExpr != null && memberAccessExpr.Name.ToString() != "Log")
{
return;
}
var memberSymbol =
context.SemanticModel.GetSymbolInfo(memberAccessExpr).Symbol as IMethodSymbol;
if (memberSymbol == null || !memberSymbol.ToString().StartsWith("AnalyzerTest.Logger.Log"))
{
return;
}
MethodDeclarationSyntax parent = GetParentMethod(context.Node);
if(parent == null)
{
return;
}
var argumentList = invocationExpr.ArgumentList;
Int32 parentArgCount = parent.ParameterList.Parameters.Count;
Int32 argCount = argumentList != null ? argumentList.Arguments.Count : 0;
if (parentArgCount != argCount)
{
var diagnostic = Diagnostic.Create(Rule, invocationExpr.GetLocation(), Description);
context.ReportDiagnostic(diagnostic);
}
}
private MethodDeclarationSyntax GetParentMethod(SyntaxNode node)
{
var parent = node.Parent as MethodDeclarationSyntax;
if(parent == null)
{
return GetParentMethod(node.Parent);
}
return parent;
}
}
}
使用系统;
使用System.Collections.Immutable;
使用Microsoft.CodeAnalysis;
使用Microsoft.CodeAnalysis.CSharp;
使用Microsoft.CodeAnalysis.CSharp.Syntax;
使用Microsoft.CodeAnalysis.Diagnostics;
使用Microsoft.CodeAnalysis.Semantics;
命名空间分析器1
{
[诊断分析仪(LanguageNames.CSharp)]
公共类日志分析器:诊断分析器
{
public const string DiagnosticId=“日志记录”;
内部常量字符串Title=“记录错误”;
内部常量字符串MessageFormat=“记录错误{0}”;
internal const string Description=“记录器中的参数数量应与方法中的参数数量相同。”;
内部常量字符串Category=“Syntax”;
内部静态诊断描述符规则=
新的DiagnosticDescriptor(DiagnosticId、标题、消息格式、,
类别,诊断严重性。错误,默认为:真,描述:描述);
公共覆盖不可变数组
支持诊断学
{get{return ImmutableArray.Create(Rule);}
公共覆盖无效初始化(AnalysisContext上下文)
{
context.RegisterSyntaxNodeAction(
AnalyzeNode,SyntaxKind.Expression);
}
专用void分析节点(SyntaxNodeAnalysisContext上下文)
{
var invocationExpr=(InvocationExpressionSyntax)context.Node;
var memberAccessExpr=invocationExpr.Expression作为MemberAccessExpressionSyntax;
if(memberAccessExpr!=null&&memberAccessExpr.Name.ToString()!=“Log”)
{
返回;
}
变量成员符号=
context.SemanticModel.GetSymbolInfo(memberAccessExpr).Symbol作为IMethodSymbol;
if(memberSymbol==null | | |!memberSymbol.ToString().StartsWith(“AnalyzerTest.Logger.Log”))
{
返回;
}
MethodDeclarationSyntax parent=GetParentMethod(context.Node);
如果(父项==null)
{
返回;
}
var argumentList=invocationExpr.argumentList;
Int32 parentArgCount=parent.ParameterList.Parameters.Count;
Int32 argCount=argumentList!=null?argumentList.Arguments.Count:0;
if(parentArgCount!=argCount)
{
var diagnostic=diagnostic.Create(规则,invocationExpr.GetLocation(),描述);
上下文。报告诊断(诊断);
}
}
private MethodDeclarationSyntax GetParentMethod(SyntaxNode节点)
{
var parent=node.parent作为MethodDeclarationSyntax;
如果(父项==null)
{
返回GetParentMethod(node.Parent);
}
返回父母;
}
}
}
在Analyzer with Code Fix项目中,您可以点击F5(只要.Vsix项目是启动项目),它将打开另一个VS实例,您可以选择要在其上测试Analyzer的项目
结果如下:
看起来您还必须将其安装为NuGet软件包而不是VS扩展,无论出于何种原因,VS扩展都不会影响构建,您只会收到警告:
有关更完整的示例,请参见此处:
远射,我自己也没用过,但这有帮助吗?您可以为内置静态分析工具创建自定义规则。将日志代码放在每个方法中似乎不是一个好的可扩展和可维护的设计。我认为您应该寻找一些用于日志记录的AOP解决方案,您不必记住在每个方法中添加日志记录,也不必记住在出现问题时更新参数列表改变
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Semantics;
namespace Analyzer1
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class LoggerAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "Logging";
internal const string Title = "Logging error";
internal const string MessageFormat = "Logging error {0}";
internal const string Description = "You should have the same amount of arguments in the logger as you do in the method.";
internal const string Category = "Syntax";
internal static DiagnosticDescriptor Rule =
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat,
Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description);
public override ImmutableArray<DiagnosticDescriptor>
SupportedDiagnostics
{ get { return ImmutableArray.Create(Rule); } }
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(
AnalyzeNode, SyntaxKind.InvocationExpression);
}
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var invocationExpr = (InvocationExpressionSyntax)context.Node;
var memberAccessExpr = invocationExpr.Expression as MemberAccessExpressionSyntax;
if (memberAccessExpr != null && memberAccessExpr.Name.ToString() != "Log")
{
return;
}
var memberSymbol =
context.SemanticModel.GetSymbolInfo(memberAccessExpr).Symbol as IMethodSymbol;
if (memberSymbol == null || !memberSymbol.ToString().StartsWith("AnalyzerTest.Logger.Log"))
{
return;
}
MethodDeclarationSyntax parent = GetParentMethod(context.Node);
if(parent == null)
{
return;
}
var argumentList = invocationExpr.ArgumentList;
Int32 parentArgCount = parent.ParameterList.Parameters.Count;
Int32 argCount = argumentList != null ? argumentList.Arguments.Count : 0;
if (parentArgCount != argCount)
{
var diagnostic = Diagnostic.Create(Rule, invocationExpr.GetLocation(), Description);
context.ReportDiagnostic(diagnostic);
}
}
private MethodDeclarationSyntax GetParentMethod(SyntaxNode node)
{
var parent = node.Parent as MethodDeclarationSyntax;
if(parent == null)
{
return GetParentMethod(node.Parent);
}
return parent;
}
}
}