执行特定数量的参数

时间:2016-12-21 20:32:49

标签: 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 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 Foo1(int a, int b, int c) {
    // Someone changed the number of parameters but didn't update the logging call
    Logger.Log(a, b);
}

Log方法的签名是:

public void Log(params object[] parameters)

我想要某种方式要求Logger.Log具有与调用它的方法相同数量的参数。

我知道如何在运行时执行此操作(只需使用反射来获取调用者的参数列表并将其与我实际收到的参数进行比较),但这对我来说是一个非常糟糕的解决方案我认为绝大多数检查是不必要的。 (这也意味着在运行时之前你不会知道你写错了,然后只有当你碰巧执行那个特定的方法时)。

现在我们不幸地不使用FxCop(或者我只是写了某种规则),我怀疑我不会成功改变这个事实。如果没有编写某种编译器插件,有没有办法强迫人们正确使用这种方法?

1 个答案:

答案 0 :(得分:3)

您应该能够使用新的Roslyn API完成此任务。您将在此处安装SDK:

https://marketplace.visualstudio.com/items?itemName=VisualStudioProductTeam.NETCompilerPlatformSDK

安装完成后,您应该转到新项目并导航到Extensibility,然后您将看到项目类型Analyzer with Code Fix(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
        }
    }
}

我为分析器创建了一个单独的项目,这里是分析器类的代码:

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;
        }
    }
}

在带有代码修复项目的分析器中,您可以按F5(只要您的.Vsix项目是启动项目),它将打开另一个VS实例,您可以选择要在其上测试分析器的项目。

结果如下:

enter image description here

看起来你必须将它安装为NuGet包而不是VS Extension,无论出于什么原因,VS Extensions不会影响构建,你只会得到警告:

https://stackoverflow.com/a/39657967/1721372

有关更完整的示例,请参阅此处:

https://msdn.microsoft.com/en-us/magazine/dn879356.aspx