C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
101: Code Analysis with Roslyn Analyzers
You've probably spent plenty of time staring at those little lightbulbs and squiggly lines in Visual Studio. Most of those are provided by the built-in .NET analyzers, but as your projects grow and your team's coding standards get more specific, you'll find that the default rules aren't enough. That's where Roslyn comes in.
Roslyn is the .NET compiler platform, and the coolest part about it is that it exposes the compiler's internal "brain" as an API. We can write our own analyzers to enforce a specific rule across a whole codebase. Today, I want to show you how to build a custom analyzer that flags the use of DateTime.Now. In my experience, using local time in a backend service is a recipe for disaster once you deploy to a server in a different timezone. We should be using DateTime.UtcNow instead.
Scaffolding the Analyzer
To get started, you'll need the "Analyzer" project template. I'm going to create a class that inherits from DiagnosticAnalyzer. The core of any analyzer is the Initialize method, where we tell Roslyn exactly which parts of the code's syntax tree we want to watch. Since we are looking for a property access, we'll register an action to trigger whenever the compiler encounters a MemberAccessExpression.
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class UtcDateTimeAnalyzer : DiagnosticAnalyzer
{
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
// We want to look at every single member access (e.g., object.Property)
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.SimpleMemberAccessExpression);
}
}
The "Naive" implementation mistake
Now, I'll write the AnalyzeNode method. My first instinct is to just check if the property being accessed is named "Now". It seems simple enough, right?
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var memberAccess = (MemberAccessExpressionSyntax)context.Node;
if (memberAccess.Name.Identifier.Text == "Now")
{
var diagnostic = Diagnostic.Create(Rule, memberAccess.Name.GetLocation());
context.ReportDiagnostic(diagnostic);
}
}
I ran this against a test project, and it was a disaster. My analyzer started flagging Order.Now, UserSession.Now, and every other property named "Now" in my entire domain model. I forgot that the syntax tree only tells me the name of the member, not its type. This is a common trap when you first start with Roslyn: confusing Syntax (the text) with Semantics (the meaning).
Adding Semantic Analysis
To fix this, I need to use the SemanticModel. This allows me to ask Roslyn, "I know this is called 'Now', but who actually owns this property?" I'll use the GetSymbolInfo method to ensure the property belongs to System.DateTime.
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var memberAccess = (MemberAccessExpressionSyntax)context.Node;
// Get the symbol information for the member being accessed
var symbolInfo = context.SemanticModel.GetSymbolInfo(memberAccess);
var symbol = symbolInfo.Symbol;
// Now we check if it's actually the DateTime.Now property
if (symbol is IPropertySymbol propertySymbol &&
propertySymbol.ContainingType.SpecialType == SpecialType.System_DateTime &&
propertySymbol.Name == "Now")
{
var diagnostic = Diagnostic.Create(Rule, memberAccess.Name.GetLocation());
context.ReportDiagnostic(diagnostic);
}
}
That's much better. Now the analyzer ignores my domain models and only screams at me when I'm about to introduce a timezone bug.
Defining the Diagnostic Rule
Finally, the analyzer needs a DiagnosticDescriptor. This is essentially the "metadata" for the error—the ID, the severity, and the message the developer sees. I've set this as a Warning rather than an Error because, while DateTime.Now is usually a mistake, there are rare cases (like UI formatting) where it's actually intended.
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
id: "UTC001",
title: "Use UtcNow instead of Now",
message: "Use DateTime.UtcNow to avoid timezone-related bugs in the backend.",
category: "Naming",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);
When you build this project and reference it as an analyzer in your main project, you'll see that warning pop up in real-time as you type. It's like having a senior dev looking over your shoulder, but without the judgmental sighs.
📋 Practical Task
Build a "No Empty Catch Block" Analyzer
Empty catch blocks are a silent killer in production—they swallow exceptions and leave you guessing why a feature stopped working. Your task is to create a custom Roslyn Analyzer that detects empty catch blocks.
Requirements:
- Register a syntax node action that targets
CatchClauseSyntax. - Check if the
Blockinside the catch clause contains any statements. - If the block is empty, report a diagnostic with the ID
CATCH001and the message: "Empty catch blocks are not allowed. Either handle the exception or rethrow it." - Set the severity to
DiagnosticSeverity.Errorto ensure the build fails if an empty catch is left in the code.
There are no comments for now.