You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C#中当secureCheck设为false时生成编译器警告(可借助Roslyn)

可行性与实现方案

Absolutely feasible! You can build a custom Roslyn Analyzer to exactly meet this requirement—this is exactly the kind of scenario Roslyn was designed for, and it works perfectly with your .NET Standard 2.0 library (C# 7.3 or 8.0 both work).

Here's a step-by-step guide to implement this:

1. Create a Roslyn Analyzer Project

In Visual Studio, create a new Analyzer with Code Fix (.NET Standard) project. This template provides the basic structure you need, including diagnostic rules, analysis logic, and optional code fix support.

2. Implement the Diagnostic Analyzer

This analyzer will scan your code for calls to CheckThing where secureCheck is explicitly set to false, and trigger a custom warning.

Here's the core analyzer code:

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 InsecureCheckAnalyzer : DiagnosticAnalyzer
{
    // Your custom warning ID (matches what you'll use in #pragma)
    public const string DiagnosticId = "InsecureCheckWarning";

    // Localized messages (use the auto-generated Resources.resx file for these)
    private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AnalyzerTitle), Resources.ResourceManager, typeof(Resources));
    private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
    private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AnalyzerDescription), Resources.ResourceManager, typeof(Resources));
    private const string Category = "Security";

    // Define the diagnostic rule (warning level, enabled by default)
    private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
        DiagnosticId, Title, MessageFormat, Category, 
        DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);

    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

    public override void Initialize(AnalysisContext context)
    {
        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
        context.EnableConcurrentExecution();

        // Register to analyze method invocation syntax nodes
        context.RegisterSyntaxNodeAction(AnalyzeInvocation, SyntaxKind.InvocationExpression);
    }

    private void AnalyzeInvocation(SyntaxNodeAnalysisContext context)
    {
        var invocationExpr = (InvocationExpressionSyntax)context.Node;

        // Get the symbol for the called method
        var methodSymbol = context.SemanticModel.GetSymbolInfo(invocationExpr).Symbol as IMethodSymbol;
        if (methodSymbol == null) return;

        // Verify this is your target method (update namespace/class name to match your code)
        if (methodSymbol.Name != "CheckThing" ||
            methodSymbol.ContainingType.Name != "YourClassName" ||
            methodSymbol.ContainingNamespace.ToString() != "Your.Project.Namespace")
            return;

        // Locate the secureCheck parameter
        var secureCheckParam = methodSymbol.Parameters.FirstOrDefault(p => p.Name == "secureCheck");
        if (secureCheckParam == null) return;

        bool isSecureCheckFalse = false;
        int paramIndex = methodSymbol.Parameters.IndexOf(secureCheckParam);

        // Check for named arguments first
        var namedArg = invocationExpr.ArgumentList.Arguments
            .OfType<NamedArgumentSyntax>()
            .FirstOrDefault(a => a.NameEquals.Name.Identifier.Text == "secureCheck");
        
        if (namedArg != null)
        {
            var constantValue = context.SemanticModel.GetConstantValue(namedArg.Expression);
            isSecureCheckFalse = constantValue.HasValue && constantValue.Value is bool b && !b;
        }
        else
        {
            // Check positional arguments (only if the parameter is explicitly provided)
            if (invocationExpr.ArgumentList.Arguments.Count > paramIndex)
            {
                var positionalArg = invocationExpr.ArgumentList.Arguments[paramIndex];
                var constantValue = context.SemanticModel.GetConstantValue(positionalArg.Expression);
                isSecureCheckFalse = constantValue.HasValue && constantValue.Value is bool b && !b;
            }
        }

        // Trigger the warning if secureCheck is set to false
        if (isSecureCheckFalse)
        {
            var diagnostic = Diagnostic.Create(Rule, invocationExpr.GetLocation(), methodSymbol.Name);
            context.ReportDiagnostic(diagnostic);
        }
    }
}

Update the Resources.resx file with these localized strings:

  • AnalyzerTitle: Insecure Security Check Usage
  • AnalyzerMessageFormat: Calling '{0}' with secureCheck=false bypasses critical security checks. Ensure this has been reviewed by the team lead.
  • AnalyzerDescription: Warns when the CheckThing method is called with secureCheck=false, indicating potential security risks that require team approval.

3. Optional: Add a Code Fix for #pragma Directives

To make it easier for your team to suppress the warning with #pragma, add a code fix provider that automatically inserts the disable/restore directives:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(InsecureCheckCodeFixProvider)), Shared]
public class InsecureCheckCodeFixProvider : CodeFixProvider
{
    public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(InsecureCheckAnalyzer.DiagnosticId);

    public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

    public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
    {
        var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
        var diagnostic = context.Diagnostics.First();
        var invocationExpr = root.FindToken(diagnostic.Location.SourceSpan.Start)
            .Parent.AncestorsAndSelf()
            .OfType<InvocationExpressionSyntax>()
            .First();

        context.RegisterCodeFix(
            CodeAction.Create(
                title: "Add #pragma warning disable for insecure check",
                createChangedDocument: c => AddPragmaDirectivesAsync(context.Document, invocationExpr, c),
                equivalenceKey: nameof(InsecureCheckCodeFixProvider)),
            diagnostic);
    }

    private async Task<Document> AddPragmaDirectivesAsync(Document document, InvocationExpressionSyntax invocationExpr, CancellationToken cancellationToken)
    {
        var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
        var statement = invocationExpr.AncestorsAndSelf().OfType<StatementSyntax>().First();

        // Create #pragma disable directive
        var disableDirective = SyntaxFactory.PragmaWarningDirectiveTrivia(
            SyntaxFactory.Token(SyntaxKind.DisableKeyword),
            SyntaxFactory.SingletonSeparatedList(SyntaxFactory.IdentifierName(InsecureCheckAnalyzer.DiagnosticId)),
            false);

        // Create #pragma restore directive
        var restoreDirective = SyntaxFactory.PragmaWarningDirectiveTrivia(
            SyntaxFactory.Token(SyntaxKind.RestoreKeyword),
            SyntaxFactory.SingletonSeparatedList(SyntaxFactory.IdentifierName(InsecureCheckAnalyzer.DiagnosticId)),
            false);

        // Add disable before the invocation statement
        var newStatement = statement.WithLeadingTrivia(
            SyntaxFactory.TriviaList(disableDirective).AddRange(statement.GetLeadingTrivia()));
        root = root.ReplaceNode(statement, newStatement);

        // Add restore after the invocation statement
        var nextSibling = statement.GetNextSibling();
        if (nextSibling != null)
        {
            nextSibling = nextSibling.WithLeadingTrivia(
                SyntaxFactory.TriviaList(restoreDirective).AddRange(nextSibling.GetLeadingTrivia()));
            root = root.ReplaceNode(nextSibling, nextSibling);
        }
        else
        {
            // If it's the last statement, add an empty statement with the restore directive
            var restoreStatement = SyntaxFactory.EmptyStatement().WithLeadingTrivia(SyntaxFactory.TriviaList(restoreDirective));
            root = root.InsertAfter(statement, restoreStatement);
        }

        // Format the modified code
        root = Formatter.Format(root, Formatter.Annotation, document.Project.Solution.Workspace);
        return document.WithSyntaxRoot(root);
    }
}

4. Integrate the Analyzer with Your Class Library

You have two options to use the analyzer in your .NET Standard 2.0 project:

  • Package as a NuGet package: Build the analyzer project, pack it into a NuGet package (using the .nuspec file generated by the template), and install it in your class library. This is the cleanest approach for shared projects.
  • Direct reference: Right-click your class library project → Add → Reference → Browse to the analyzer project's bin/Debug or bin/Release folder, and select the analyzer DLL.

5. Test the Workflow

Once integrated:

  1. When you call CheckThing(a, b, secureCheck: false), you'll see your custom InsecureCheckWarning in the Error List.
  2. Use the code fix (Ctrl+. or right-click the warning) to auto-add the #pragma directives, or manually write them as you described.
  3. The warning will be suppressed only where you've added the #pragma disable and restored afterward.

This setup ensures that any use of the insecure mode is explicitly flagged, requiring team review and documentation via the #pragma comments.

内容的提问来源于stack exchange,提问作者TC Fox

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:04:52