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 the most common—and most dangerous—mistakes when building streaming data pipelines:

Code RangeCategoryPurpose
NP90XXResilienceEnforces proper error handling and recovery configuration
NP91XXPerformanceCatches blocking operations, non-streaming patterns, and async/await anti-patterns
NP92XXData FlowEnsures proper data consumption and processing patterns
NP93XXReliabilityDetects inefficient exception handling and unsafe access patterns
NP94XXBest PracticesValidates dependency injection, resource management, and framework contract compliance
NP95XXConfigurationDetects configuration issues that can cause performance problems or silent failures

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

Quick Reference: All Analyzer Codes

CodeCategoryProblemFix
NP9001ResilienceIncomplete resilience configurationAdd missing prerequisites
NP9101PerformanceBlocking operations in async methodsUse await instead of .Result/.Wait()
NP9102PerformanceSwallowed OperationCanceledExceptionRe-throw or handle explicitly
NP9103PerformanceSynchronous over async (fire-and-forget)Await the async call
NP9104PerformanceDisrespecting cancellation tokenCheck token and propagate
NP9201PerformanceLINQ operations in hot pathsUse imperative alternatives
NP9202PerformanceInefficient string operationsUse StringBuilder, interpolation, or spans
NP9203PerformanceAnonymous object allocation in hot pathsUse named types or value types
NP9204PerformanceMissing ValueTask optimizationUse ValueTask for sync-heavy paths
NP9205PerformanceNon-streaming patterns in SourceNodeUse IAsyncEnumerable with yield
NP9301ReliabilityInefficient exception handling patternsUse specific exception handling
NP9302Data ProcessingSinkNode input not consumedIterate the input pipe
NP9303Best PracticeUnsafe PipelineContext accessUse null-safe patterns
NP9401Best PracticeDirect dependency instantiationUse constructor injection
NP9501ConfigurationUnbounded materialization configurationSet MaxMaterializedItems value
NP9502ConfigurationInappropriate parallelism configurationMatch parallelism to workload
NP9503ConfigurationBatching configuration mismatchAlign batch size and timeout
NP9504ConfigurationTimeout configuration issuesSet appropriate timeouts

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.NP9101.severity = error
dotnet_diagnostic.NP9103.severity = error
dotnet_diagnostic.NP9201.severity = error
dotnet_diagnostic.NP9202.severity = error
dotnet_diagnostic.NP9203.severity = error
dotnet_diagnostic.NP9205.severity = error
dotnet_diagnostic.NP9301.severity = error
dotnet_diagnostic.NP9302.severity = error
dotnet_diagnostic.NP9303.severity = error
dotnet_diagnostic.NP9401.severity = error
dotnet_diagnostic.NP9501.severity = error
dotnet_diagnostic.NP9502.severity = warning
dotnet_diagnostic.NP9503.severity = warning
dotnet_diagnostic.NP9504.severity = warning

See Also