Skip to main content

Build-Time Analyzers

Build-Time Guardrails: Automated Enforcement of Best Practices

The NPipeline.Analyzers package provides a comprehensive suite of build-time Roslyn analyzers that act as automated guardrails for code quality. Rather than treating them as just another error reference, these analyzers are the framework's proactive enforcement mechanism for ensuring your pipeline implementations follow the design principles that make NPipeline fast, safe, and reliable.

What Are Build-Time Analyzers?

Build-time analyzers are diagnostic tools that scan your code at compile time—before it ever runs—to detect violations of the framework's fundamental design contract. They catch issues that would otherwise surface as runtime failures, silent data loss, performance bottlenecks, or maintenance headaches.

Think of them as automated code review by experts who understand how high-performance streaming systems should work.

The NP9000 Series: Performance and Resilience Hygiene Toolkit

The NP9000 series (NP9XXX) diagnostics represent a curated set of enforcement rules that protect you from most common—and most dangerous—mistakes when building streaming data pipelines:

Code RangeCategoryPurpose
NP90XXConfiguration & SetupEnforces proper pipeline configuration for resilience, parallelism, batching, and timeouts
NP91XXPerformance & OptimizationCatches blocking operations, non-streaming patterns, and async/await anti-patterns
NP92XXReliability & Error HandlingEnsures proper error handling, cancellation propagation, and exception management
NP93XXData Integrity & CorrectnessDetects unsafe access patterns and ensures proper data consumption
NP94XXDesign & ArchitectureValidates dependency injection, node design, and framework contract compliance

Why This Matters

Without these analyzers, developers could:

  • ✗ Configure error handlers to restart nodes without the required prerequisites, causing silent failures
  • ✗ Block on async code, causing deadlocks and thread pool starvation
  • ✗ Build non-streaming SourceNodes that allocate gigabytes of memory for large datasets
  • ✗ Inject dependencies unsafely, creating tightly coupled code that's hard to test
  • ✗ Forget to consume input in SinkNodes, silently dropping data
  • ✗ Access PipelineContext unsafely, causing null reference exceptions at runtime

With these analyzers, all of these issues are caught at build time, not at 3 AM in production.

The Problem These Analyzers Solve

Problematic Code: Silent Failures at Runtime

// Looks correct but will fail silently at runtime
public class MyErrorHandler : IPipelineErrorHandler
{
public async Task<PipelineErrorDecision> HandleNodeFailureAsync(
string nodeId,
Exception error,
PipelineContext context,
CancellationToken cancellationToken)
{
return error switch
{
TimeoutException => PipelineErrorDecision.RestartNode, // Intent is clear
_ => PipelineErrorDecision.FailPipeline
};
}
}

// But at runtime, restart silently fails because prerequisites are missing!
// The entire pipeline crashes instead of gracefully restarting the node.

Solution: Build-Time Enforcement

CSC : warning NP9001: Error handler can return PipelineErrorDecision.RestartNode
but the node may not have all three mandatory prerequisites configured...

You fix it during development, not during a production incident.

Analyzer Categories

The analyzers are organized into focused sections based on what they protect:

Installation

The analyzer is included with the main NPipeline package:

dotnet add package NPipeline

Or install it separately:

dotnet add package NPipeline.Analyzers

For all available NPipeline packages and installation options, see the Installation Guide.

Quick Reference: All Analyzer Codes

CodeCategoryProblemFix
NP9001Configuration & SetupIncomplete resilience configurationAdd missing prerequisites
NP9002Configuration & SetupUnbounded materialization configurationSet MaxMaterializedItems value
NP9003Configuration & SetupInappropriate parallelism configurationMatch parallelism to workload
NP9004Configuration & SetupBatching configuration mismatchAlign batch size and timeout
NP9005Configuration & SetupTimeout configuration issuesSet appropriate timeouts
NP9101Performance & OptimizationBlocking operations in async methodsUse await instead of .Result/.Wait()
NP9102Performance & OptimizationSynchronous over async (fire-and-forget)Await the async call
NP9103Performance & OptimizationLINQ operations in hot pathsUse imperative alternatives
NP9104Performance & OptimizationInefficient string operationsUse StringBuilder, interpolation, or spans
NP9105Performance & OptimizationAnonymous object allocation in hot pathsUse named types or value types
NP9106Performance & OptimizationMissing ValueTask optimizationUse ValueTask for sync-heavy paths
NP9107Performance & OptimizationNon-streaming patterns in SourceNodeUse IAsyncEnumerable with yield
NP9108Performance & OptimizationParameterless constructor performance suggestionAdd parameterless constructor
NP9201Reliability & Error HandlingSwallowed OperationCanceledExceptionRe-throw or handle explicitly
NP9202Reliability & Error HandlingInefficient exception handling patternsUse specific exception handling
NP9203Reliability & Error HandlingDisrespecting cancellation tokenCheck token and propagate
NP9301Data Integrity & CorrectnessSinkNode input not consumedIterate through input pipe
NP9302Data Integrity & CorrectnessUnsafe PipelineContext accessUse null-safe patterns
NP9401Design & ArchitectureITransformNode returning IAsyncEnumerableUse IStreamTransformNode instead
NP9402Design & ArchitectureIStreamTransformNode with non-stream strategyUse IStreamExecutionStrategy
NP9403Design & ArchitectureMissing parameterless constructorAdd parameterless constructor
NP9404Design & ArchitectureDirect dependency instantiationUse constructor injection

Philosophy

These analyzers embody a core principle: Getting it right before you compile is infinitely better than fixing it after it breaks in production.

They enforce the framework's fundamental design contract:

  • Resilience: Error handling must be configured completely or not at all
  • Performance: Streaming operations must never block or materialize unnecessarily
  • Safety: Dependencies must be explicit and context access must be protected
  • Correctness: Data flow must be complete and cancellation must be respected

Best Practices

  1. Never suppress warnings without understanding why - The analyzer is protecting you from real problems
  2. Treat warnings as errors - Consider configuring .editorconfig to make violations errors instead of warnings
  3. Use the analyzers as a learning tool - Each warning teaches you something about building safe, fast pipelines
  4. Apply fixes during development - It's always cheaper to fix issues at compile time

Configuration

You can adjust analyzer severity in your .editorconfig:

# Treat all analyzer warnings as errors
dotnet_diagnostic.NP9001.severity = error
dotnet_diagnostic.NP9002.severity = error
dotnet_diagnostic.NP9101.severity = error
dotnet_diagnostic.NP9102.severity = error
dotnet_diagnostic.NP9103.severity = error
dotnet_diagnostic.NP9107.severity = error
dotnet_diagnostic.NP9201.severity = error
dotnet_diagnostic.NP9202.severity = error
dotnet_diagnostic.NP9301.severity = error
dotnet_diagnostic.NP9302.severity = error
dotnet_diagnostic.NP9401.severity = error
dotnet_diagnostic.NP9404.severity = error

See Also