# Tooling and Ecosystem Standards for C#
This document outlines the recommended tooling and ecosystem standards for C# development. These standards aim to promote consistency, maintainability, and efficiency in our C# projects by leveraging the .NET ecosystem effectively. They are designed to be used in conjunction with AI coding assistants to ensure high-quality code generation and adherence to best practices.
## 1. Integrated Development Environment (IDE)
### 1.1. Recommended IDE: Visual Studio
**Do This:** Use the latest version of Visual Studio (or Visual Studio Code with the C# extension) for your C# development.
**Don't Do This:** Use outdated versions of Visual Studio or other IDEs that lack full support for the latest C# features and debugging capabilities.
**Why:** Visual Studio provides comprehensive support for C#, including IntelliSense, debugging, profiling, and integrated testing. The latest version ensures access to the newest language features, performance improvements, and security updates.
**Example:**
"""csharp
// Visual Studio with C# extension example
using System;
namespace ExampleNamespace
{
public class ExampleClass
{
public static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
}
"""
### 1.2. VS Code with C# Extension
**Do This:** If using VS Code, install the official C# extension from Microsoft for comprehensive C# support.
**Don't Do This:** Rely on minimal or incomplete C# extensions that don't provide full language support.
**Why:** The official C# extension provides IntelliSense, debugging, refactoring, and other features crucial for efficient C# development.
**Example:**
Install the C# extension from the VS Code marketplace. Configure OmniSharp for optimal performance.
## 2. Package Management
### 2.1. NuGet Package Manager
**Do This:** Use NuGet for managing project dependencies.
**Don't Do This:** Manually include DLL files or use outdated package management approaches.
**Why:** NuGet simplifies the process of adding, updating, and removing dependencies, ensuring that projects use the correct versions of libraries and tools.
**Example:**
"""powershell
# Install a package using NuGet Package Manager Console
Install-Package Newtonsoft.Json -Version 13.0.1
"""
### 2.2. Package Versioning
**Do This:** Use semantic versioning to specify package versions in your project files. Consider using version ranges for minor updates.
**Don't Do This:** Use wildcard versions (e.g., "1.*") or overly restrictive version constraints that prevent necessary updates.
**Why:** Semantic versioning helps avoid breaking changes and ensures compatibility between different components.
**Example:**
"""xml
"""
### 2.3. Central Package Management (CPM)
**Do This:** Utilize Central Package Management (CPM) introduced in .NET 6 and improved in later versions for managing dependencies across multiple projects in a solution.
**Don't Do This:** Manage package versions individually in each project, leading to inconsistencies.
**Why:** CPM enables consistent versioning and reduces the complexity of managing dependencies in large solutions.
**Example:**
In "Directory.Packages.props":
"""xml
true
"""
In each project's ".csproj":
"""xml
net6.0
enable
enable
"""
## 3. Code Analysis Tools
### 3.1. Roslyn Analyzers
**Do This:** Use Roslyn analyzers to enforce coding standards, detect potential bugs, and improve code quality.
**Don't Do This:** Ignore analyzer warnings and suggestions, leading to technical debt and maintenance issues.
**Why:** Roslyn analyzers provide real-time feedback on code quality and help prevent common errors.
**Example:** Install and configure analyzers like "StyleCop.Analyzers", "SonarAnalyzer.CSharp", or "Microsoft.CodeAnalysis.NetAnalyzers".
"""xml
"""
Configure rules in "stylecop.json" or ".editorconfig".
### 3.2. .editorconfig
**Do This:** Use ".editorconfig" to define coding styles and formatting rules for your project.
**Don't Do This:** Rely on individual developer settings, which can lead to inconsistent formatting.
**Why:** ".editorconfig" ensures consistent code formatting across the entire team, regardless of individual IDE settings.
**Example:**
"""editorconfig
# Top-most EditorConfig file
root = true
# C# files
[*.cs]
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
# Analyzer settings
dotnet_diagnostic.CS8019.severity = suggestion #Unnecessary using directive
"""
### 3.3. Static Code Analysis Tools
**Do This:** Integrate static code analysis tools like SonarQube or Coverity into your build process to detect vulnerabilities and code quality issues.
**Don't Do This:** Skip static analysis, leaving potential security flaws and performance bottlenecks undetected.
**Why:** Static analysis tools provide comprehensive code reviews and identify issues that might be missed during manual reviews.
**Example:** Configure SonarQube analysis in your CI/CD pipeline.
## 4. Testing Frameworks
### 4.1. xUnit, NUnit, MSTest
**Do This:** Use a reputable testing framework like xUnit, NUnit, or MSTest for writing unit tests.
**Don't Do This:** Avoid writing tests or use outdated testing approaches.
**Why:** Automated testing is crucial for ensuring code quality, preventing regressions, and facilitating refactoring.
**Example (xUnit):**
"""csharp
using Xunit;
public class CalculatorTests
{
[Fact]
public void Add_TwoNumbers_ReturnsSum()
{
var calculator = new Calculator();
int result = calculator.Add(2, 3);
Assert.Equal(5, result);
}
}
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
}
"""
### 4.2. Mocking Frameworks
**Do This:** Use mocking frameworks like Moq or NSubstitute to isolate units of code during testing.
**Don't Do This:** Directly depend on external resources or complex dependencies in your unit tests.
**Why:** Mocking allows you to test code in isolation, ensuring that tests are fast, reliable, and focused on the specific functionality being tested.
**Example (Moq):**
"""csharp
using Moq;
using Xunit;
public interface IDataService
{
string GetData();
}
public class DataConsumer
{
private readonly IDataService _dataService;
public DataConsumer(IDataService dataService)
{
_dataService = dataService;
}
public string ProcessData()
{
string data = _dataService.GetData();
return $"Processed: {data}";
}
}
public class DataConsumerTests
{
[Fact]
public void ProcessData_ReturnsProcessedData()
{
// Arrange
var mockDataService = new Mock();
mockDataService.Setup(ds => ds.GetData()).Returns("Test Data");
var dataConsumer = new DataConsumer(mockDataService.Object);
// Act
string result = dataConsumer.ProcessData();
// Assert
Assert.Equal("Processed: Test Data", result);
mockDataService.Verify(ds => ds.GetData(), Times.Once);
}
}
"""
### 4.3. Test Driven Development (TDD)
**Do This:** Consider using TDD to guide your development process.
**Don't Do This:** Neglect writing tests until late in the development cycle.
**Why:** TDD helps ensure that your code is testable and meets the required specifications.
## 5. Logging and Diagnostics
### 5.1. Microsoft.Extensions.Logging
**Do This:** Use "Microsoft.Extensions.Logging" for structured logging in your applications. Embrace logging levels and categories effectively.
**Don't Do This:** Use "Console.WriteLine" for general logging purposes.
**Why:** "Microsoft.Extensions.Logging" provides a flexible and extensible logging framework that can be configured to write logs to various destinations.
**Example:**
"""csharp
using Microsoft.Extensions.Logging;
public class ExampleClass
{
private readonly ILogger _logger;
public ExampleClass(ILogger logger)
{
_logger = logger;
}
public void DoSomething(string input)
{
_logger.LogInformation("Doing something with input: {Input}", input);
try
{
//Some code here
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while doing something.");
}
}
}
"""
### 5.2. Serilog and Seq
**Do This:** Integrate Serilog for advanced logging scenarios, especially when paired with Seq for structured log viewing and analysis.
**Don't Do This:** Fail to centralize and analyze your application logs.
**Why:** Serilog provides rich logging features, and Seq allows you to easily search, filter, and analyze your logs.
**Example:**
"""csharp
using Serilog;
using Microsoft.Extensions.Hosting;
public class Program
{
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.Seq("http://localhost:5341") // Seq URL
.CreateLogger();
try
{
Log.Information("Starting web host");
CreateHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup();
});
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddSerilog();
//...other config
}
}
"""
### 5.3. Application Insights
**Do This:** Use Application Insights for monitoring and troubleshooting your applications in production.
**Don't Do This:** Deploy applications without proper monitoring.
**Why:** Application Insights provides detailed telemetry data, including performance metrics, exceptions, and usage patterns.
**Example:** Configure Application Insights in your Azure deployment.
## 6. Build Automation
### 6.1. MSBuild, .NET CLI
**Do This:** Use MSBuild or the .NET CLI for building and deploying your C# projects.
**Don't Do This:** Manually build projects or use outdated build systems.
**Why:** MSBuild and the .NET CLI provide a consistent and reliable way to build and deploy C# projects.
**Example:**
"""powershell
# Build a project using the .NET CLI
dotnet build
"""
"""powershell
# Publish a project for deployment
dotnet publish -c Release -o ./publish
"""
### 6.2. CI/CD Pipelines
**Do This:** Set up CI/CD pipelines using tools like Azure DevOps, GitHub Actions, or Jenkins to automate the build, test, and deployment process.
**Don't Do This:** Manually deploy applications or skip automated testing in your deployment process.
**Why:** CI/CD pipelines ensure that code is automatically built, tested, and deployed, reducing the risk of errors and improving the speed of delivery.
**Example (GitHub Actions):**
"""yaml
name: .NET Build and Deploy
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 6.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal
- name: Publish
run: dotnet publish -c Release -o ./publish
- name: Deploy to Azure App Service
uses: azure/webapps-deploy@v2
with:
app-name: 'your-app-name'
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
package: ./publish
"""
## 7. Code Documentation
### 7.1. XML Documentation Comments
**Do This:** Use XML documentation comments to document your code.
**Don't Do This:** Neglect documenting public APIs, hindering usability and maintainability.
**Why:** XML documentation comments provide valuable information about your code, which can be used by IntelliSense and documentation generators.
**Example:**
"""csharp
///
/// Adds two numbers and returns the sum.
///
/// The first number.
/// The second number.
/// The sum of a and b.
public int Add(int a, int b)
{
return a + b;
}
"""
### 7.2. Documentation Generators
**Do This:** Use documentation generators like Sandcastle or DocFX to create API documentation from XML documentation comments.
**Don't Do This:** Rely solely on inline comments without generating formal documentation.
**Why:** Documentation generators create professional-looking API documentation that can be easily shared with other developers.
## 8. Advanced Tooling and Techniques
### 8.1. Source Generators
**Do This:** Employ source generators for compile-time code generation, especially for boilerplate code or performance-critical scenarios (introduced in C# 9).
**Don't Do This:** Overuse reflection or runtime code generation when source generators offer a more efficient alternative.
**Why:** Source generators improve performance by generating code at compile time, reducing runtime overhead.
**Example:** A basic source generator that adds a "GenerateHelloMethod" to any class with the "GenerateHelloAttribute".
"""csharp
// Attribute declaration
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class GenerateHelloAttribute : Attribute { }
"""
"""csharp
// Source Generator implementation
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
[Generator]
public class HelloSourceGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
// Find the main method
var syntaxTrees = context.Compilation.SyntaxTrees;
var compilation = context.Compilation;
// find all classes with the GenerateHelloAttribute
IEnumerable helloClasses = syntaxTrees
.SelectMany(syntaxTree => syntaxTree.GetRoot().DescendantNodes().OfType()
.Where(cds => cds.AttributeLists
.Any(al => al.Attributes
.Any(att => compilation.GetTypeByMetadataName("GenerateHelloAttribute") != null && att.Name.ToString() == "GenerateHello"))));
foreach (var classSyntax in helloClasses)
{
// Get the semantic model
SemanticModel semanticModel = context.Compilation.GetSemanticModel(classSyntax.SyntaxTree);
if (semanticModel.GetDeclaredSymbol(classSyntax) is not ITypeSymbol classSymbol) continue;
string namespaceName = classSymbol.ContainingNamespace.ToString();
string className = classSyntax.Identifier.Text;
// Generate the source code
string sourceCode = $@"
namespace {namespaceName}
{{
public partial class {className}
{{
public string GenerateHelloMethod()
{{
return ""Hello from the source generator!"";
}}
}}
}}";
// Add the source code to the compilation
context.AddSource($"{className}_generated.cs", sourceCode);
}
}
public void Initialize(GeneratorInitializationContext context)
{
// No initialization required for this example
}
}
"""
To use the source generator:
1. Create a new .NET project (e.g., a class library).
2. Install the "Microsoft.CodeAnalysis.CSharp.Workspaces" NuGet package.
3. Add the "HelloSourceGenerator.cs" file to the project.
4. Create or modify a consumer project.
"""xml
"""
"""csharp
// In the consumer project
[GenerateHello] // Apply the attribute
public partial class MyClass { }
public class Program {
public static void Main(string[] args) {
MyClass obj = new MyClass();
Console.WriteLine(obj.GenerateHelloMethod()); // Output: Hello from the source generator!
}
}
"""
### 8.2. Code Contract Annotations
**Do This:** Explore code contract annotations to express preconditions, postconditions, and object invariants. (Consider this more for legacy/existing larger systems)
**Don't Do This:** Rely solely on exceptions for validation checks, especially for critical business logic.
**Why:** Code contracts facilitate static verification, runtime checking, and documentation of code behavior.
### 8.3. Benchmarking Tools
**Do This:** Use benchmarking tools like BenchmarkDotNet to measure the performance of your code.
**Don't Do This:** Rely on anecdotal evidence or intuition when optimizing performance.
**Why:** Benchmarking provides accurate and objective data for identifying performance bottlenecks and verifying optimizations.
**Example:**
"""csharp
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
public class StringConcatBenchmark
{
private const int N = 1000;
[Benchmark]
public string StringConcat()
{
string result = "";
for (int i = 0; i < N; i++)
{
result += "a";
}
return result;
}
[Benchmark]
public string StringBuilderConcat()
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < N; i++)
{
sb.Append("a");
}
return sb.ToString();
}
}
public class Program
{
public static void Main(string[] args)
{
var summary = BenchmarkRunner.Run();
}
}
"""
## 9. Version Control
### 9.1. Git
**Do This:** Utilize Git for version control.
**Don't Do This:** Use older version control systems or share code without version control.
**Why:** Git allows for effective code management, collaboration, and tracking of changes.
### 9.2. Branching Strategies
**Do This:** Use established branching strategies such as Gitflow or GitHub Flow.
**Don't Do This:** Commit directly to the main branch without proper review and testing.
**Why:** Following a good branching strategy ensures code quality, facilitates collaboration, and reduces integration conflicts.
## 10. Security Tools
### 10.1. Security Analyzers
**Do This:** Use security analyzers to identify vulnerabilities in your code.
**Don't Do This:** Ignore security warnings, especially when dealing with user input or sensitive data. Consider tools like SonarQube or specialized security analyzers for .NET.
**Why:** Security analyzers help prevent common security flaws that can be exploited by attackers.
### 10.2. Dependency Scanning
**Do This:** Regularly scan your project dependencies for known vulnerabilities using tools like OWASP Dependency-Check or Snyk.
**Don't Do This:** Use outdated or vulnerable dependencies in your projects.
**Why:** Dependency scanning helps identify and mitigate security risks associated with third-party libraries.
By adhering to these tooling and ecosystem standards, we can ensure that our C# projects are well-maintained, efficient, and secure. This guide will be continually updated to reflect the latest best practices and technological advancements in the .NET ecosystem.
danielsogl
Created Mar 6, 2025
This guide explains how to effectively use .clinerules
with Cline, the AI-powered coding assistant.
The .clinerules
file is a powerful configuration file that helps Cline understand your project's requirements, coding standards, and constraints. When placed in your project's root directory, it automatically guides Cline's behavior and ensures consistency across your codebase.
Place the .clinerules
file in your project's root directory. Cline automatically detects and follows these rules for all files within the project.
# Project Overview project: name: 'Your Project Name' description: 'Brief project description' stack: - technology: 'Framework/Language' version: 'X.Y.Z' - technology: 'Database' version: 'X.Y.Z'
# Code Standards standards: style: - 'Use consistent indentation (2 spaces)' - 'Follow language-specific naming conventions' documentation: - 'Include JSDoc comments for all functions' - 'Maintain up-to-date README files' testing: - 'Write unit tests for all new features' - 'Maintain minimum 80% code coverage'
# Security Guidelines security: authentication: - 'Implement proper token validation' - 'Use environment variables for secrets' dataProtection: - 'Sanitize all user inputs' - 'Implement proper error handling'
Be Specific
Maintain Organization
Regular Updates
# Common Patterns Example patterns: components: - pattern: 'Use functional components by default' - pattern: 'Implement error boundaries for component trees' stateManagement: - pattern: 'Use React Query for server state' - pattern: 'Implement proper loading states'
Commit the Rules
.clinerules
in version controlTeam Collaboration
Rules Not Being Applied
Conflicting Rules
Performance Considerations
# Basic .clinerules Example project: name: 'Web Application' type: 'Next.js Frontend' standards: - 'Use TypeScript for all new code' - 'Follow React best practices' - 'Implement proper error handling' testing: unit: - 'Jest for unit tests' - 'React Testing Library for components' e2e: - 'Cypress for end-to-end testing' documentation: required: - 'README.md in each major directory' - 'JSDoc comments for public APIs' - 'Changelog updates for all changes'
# Advanced .clinerules Example project: name: 'Enterprise Application' compliance: - 'GDPR requirements' - 'WCAG 2.1 AA accessibility' architecture: patterns: - 'Clean Architecture principles' - 'Domain-Driven Design concepts' security: requirements: - 'OAuth 2.0 authentication' - 'Rate limiting on all APIs' - 'Input validation with Zod'
# Performance Optimization Standards for C# This document outlines performance optimization standards for C# development. It aims to guide developers in writing efficient and responsive applications by providing actionable guidelines, code examples, and explanations of underlying principles. These standards are designed to work with the latest versions of C# and the .NET ecosystem. ## 1. Architectural Considerations ### 1.1. Choose the Right Architecture * **Do This**: Select an architecture that aligns with the application's requirements and anticipated scale. Consider microservices, layered architecture, or event-driven architecture based on project needs. * **Don't Do This**: Blindly adopt an architecture without assessing its suitability for the project. Over-engineering or under-engineering can both lead to performance bottlenecks. * **Why**: Choosing the wrong architecture can result in poor scalability, increased complexity, and reduced performance. **Example:** """csharp // Microservice architecture example (High level concept) // Multiple services communicating via message queue (e.g. RabbitMQ, Azure Service Bus) // Order Service public class OrderService { private readonly IMessageQueue _messageQueue; public OrderService(IMessageQueue messageQueue) { _messageQueue = messageQueue; } public void PlaceOrder(Order order) { // Process order // ... _messageQueue.Publish("order.placed", order); } } """ ### 1.2. Asynchronous Programming * **Do This**: Utilize asynchronous programming ("async"/"await") for I/O-bound operations such as network requests, file access, and database queries. * **Don't Do This**: Block the main thread on I/O operations. This can lead to UI freezes and decreased responsiveness. * **Why**: Asynchronous operations free up threads to handle other tasks while waiting for I/O to complete. **Example:** """csharp // Good: Asynchronous network request public async Task<string> GetWebPageContentAsync(string url) { using HttpClient client = new HttpClient(); return await client.GetStringAsync(url); } // Bad: Synchronous network request (blocking) public string GetWebPageContent(string url) { using HttpClient client = new HttpClient(); return client.GetStringAsync(url).Result; // Avoid .Result and .Wait() as it blocks } """ * If CPU bound use "Task.Run" but understand the consequences of thread pool exhaustion. """csharp public async Task<int> CalculateAsync(int input) { return await Task.Run(() => LongRunningCalculation(input)); } """ ### 1.3. Caching Strategies * **Do This**: Implement caching at different levels (e.g., in-memory, distributed cache) to reduce database load and improve response times. * **Don't Do This**: Overuse caching or cache data that changes frequently. Incorrect cache invalidation can lead to stale data. * **Why**: Caching stores frequently accessed data in a faster medium, reducing the need to repeatedly fetch it from slower sources. **Example:** """csharp // In-memory caching using MemoryCache using Microsoft.Extensions.Caching.Memory; public class ProductService { private readonly IMemoryCache _cache; public ProductService(IMemoryCache cache) { _cache = cache; } public Product GetProduct(int id) { return _cache.GetOrCreate(id, entry => { entry.SlidingExpiration = TimeSpan.FromMinutes(5); // Fetch product from database return FetchProductFromDatabase(id); }); } private Product FetchProductFromDatabase(int id) { // Database logic return new Product { Id = id, Name = "Example Product" }; } } """ ### 1.4. Load Balancing * **Do This**: Distribute incoming traffic across multiple servers using a load balancer. * **Don't Do This**: Rely on a single server to handle all the load. This creates a single point of failure and limits scalability. * **Why**: Load balancing improves availability and performance by ensuring no single server is overwhelmed. ## 2. Code-Level Optimizations ### 2.1. String Manipulation * **Do This**: Use "StringBuilder" for concatenating strings in loops or when performing multiple modifications. Use "string.Create" when constructing immutable strings from spans. Use "InterpolatedStringHandler" for optimizing string interpolation scenarios. * **Don't Do This**: Use the "+" operator for repeatedly concatenating strings within loops. This creates multiple string instances, leading to performance overhead. * **Why**: "StringBuilder" is mutable and avoids creating numerous temporary string objects. **Example:** """csharp // Good: Using StringBuilder using System.Text; public string BuildString(string[] parts) { StringBuilder sb = new StringBuilder(); foreach (string part in parts) { sb.Append(part); } return sb.ToString(); } // Bad: Using + operator inefficiently public string ConcatenateStrings(string[] parts) { string result = ""; foreach (string part in parts) { result += part; //Inneficient string operations. } return result; } // Better: Using String.Create public static string CreateStringFromChars(ReadOnlySpan<char> charArray) { return string.Create(charArray.Length, charArray, (span, state) => { state.CopyTo(span); }); } // Best: InterpolatedStringHandler - Custom handler for optimized interpolation using System.Runtime.CompilerServices; [InterpolatedStringHandler] public ref struct ImportantMessageInterpolationHandler { private StringBuilder _builder; private bool _enabled; public ImportantMessageInterpolationHandler(int literalLength, int formattedCount, out bool isEnabled) { _enabled = DateTime.Now.Hour >= 9 && DateTime.Now.Hour <= 17; // Example condition isEnabled = _enabled; if (_enabled) { _builder = new StringBuilder(literalLength + formattedCount * 10); // Adjust capacity as needed } else { _builder = null; } } public void AppendLiteral(string literal) { if (_enabled) { _builder.Append(literal); } } public void AppendFormatted(string value) { if (_enabled) { _builder.Append(value); } } public void AppendFormatted(int value) { if (_enabled) { _builder.Append(value); } } internal string GetFormattedText() => _builder?.ToString(); } public static class Logger { public static void LogImportantMessage(ImportantMessageInterpolationHandler message) { string formattedMessage = message.GetFormattedText(); if (!string.IsNullOrEmpty(formattedMessage)) { Console.WriteLine($"Important: {formattedMessage}"); } } } // Usage string name = "Alice"; int age = 30; Logger.LogImportantMessage($"User {name} is {age} years old."); //Optimized due to handler """ ### 2.2. Boxing and Unboxing * **Do This**: Avoid unnecessary boxing and unboxing by using generics and strongly typed collections. * **Don't Do This**: Store value types (e.g., "int", "bool", "struct") in non-generic collections like "ArrayList". * **Why**: Boxing creates a heap-allocated object for value types, leading to performance overhead. Unboxing retrieves the value from the heap object. **Example:** """csharp // Good: Using generic List List<int> numbers = new List<int>(); numbers.Add(1); // No boxing // Bad: Using ArrayList System.Collections.ArrayList values = new System.Collections.ArrayList(); values.Add(1); // Boxing occurs """ ### 2.3. LINQ Usage * **Do This**: Use LINQ judiciously, especially for small to medium-sized collections. Consider the performance implications of complex LINQ queries. Use struct enumerators when available. * **Don't Do This**: Overuse LINQ for simple operations or when performance is critical. Use imperative code for optimized performance when necessary. Be mindful of deferred execution. * **Why**: LINQ can be convenient, but its deferred execution and overhead can impact performance in certain scenarios. **Example:** """csharp // Good: Using LINQ for a small collection List<int> numbers = new List<int> { 1, 2, 3, 4, 5 }; var evenNumbers = numbers.Where(n => n % 2 == 0).ToList(); // When needing to optimize where LINQ is not efficient // Use struct enumerators for zero allocations public readonly struct MyCustomCollection { private readonly int[] _data; public MyCustomCollection(int[] data) => _data = data; public Enumerator GetEnumerator() => new Enumerator(_data); public struct Enumerator //Struct Enumerator, Value Type, no boxing { private readonly int[] _data; private int _index; internal Enumerator(int[] data) { _data = data; _index = -1; } public bool MoveNext() => ++_index < _data.Length; public int Current => _data[_index]; } } """ ### 2.4. Reflection * **Do This**: Minimize the use of reflection, as it is slower than direct code execution. Cache reflection results when possible. * **Don't Do This**: Use reflection excessively in performance-critical sections of code. * **Why**: Reflection involves runtime type discovery and dynamic method invocation, which are computationally expensive. **Example:** """csharp // Good: Caching reflection results using System.Reflection; public class ReflectionExample { private static readonly MethodInfo MyMethodInfo = typeof(MyClass).GetMethod("MyMethod"); public void CallMyMethod(MyClass obj) { MyMethodInfo.Invoke(obj, null); // Invoke cached method directly } } public class MyClass { public void MyMethod() { // Method implementation } } """ ### 2.5. Value Types vs. Reference Types * **Do This**: Use value types ("struct") for small, immutable data structures with value semantics to avoid heap allocations. Use "readonly struct" for maximum immutability and performance. * **Don't Do This**: Use value types for large or mutable data structures that are frequently passed around. * **Why**: Value types are allocated on the stack, reducing garbage collection pressure. **Example:** """csharp // Good: ReadOnly struct for immutable data public readonly struct Point { public readonly int X; public readonly int Y; public Point(int x, int y) { X = x; Y = y; } } """ ### 2.6. Dispose Resources * **Do This:** Implement the "IDisposable" interface for classes that manage unmanaged resources (e.g., file handles, network connections). Use "using" statements or try-finally blocks to ensure resources are disposed of properly. * **Don't Do This:** Neglect to dispose of resources, leading to memory leaks and performance degradation. * **Why**: Improper resource management can exhaust system resources and negatively impact application performance. **Example:** """csharp // Good: Using 'using' statement for resource disposal public void ProcessFile(string filePath) { using (StreamReader reader = new StreamReader(filePath)) { string content = reader.ReadToEnd(); // Process file content } // StreamReader is disposed here } //Good: Using IAsyncDisposable with asynchronous operations public class MyAsyncDisposable : IAsyncDisposable { private HttpClient _httpClient; public MyAsyncDisposable() { _httpClient = new HttpClient(); } public async ValueTask DisposeAsync() { if (_httpClient != null) { _httpClient.Dispose(); _httpClient = null; } await Task.CompletedTask; // Placeholder for any async cleanup } } public async Task UseAsyncDisposable() { await using (var myObject = new MyAsyncDisposable()) { //Use the async disposable object. } // DisposeAsync called automatically once outside scope. } """ ### 2.7. Collections * **Do This:** Choose the appropriate collection type based on usage patterns. Use "List<T>" for general-purpose collections, "Dictionary<TKey, TValue>" for key-value lookups, "HashSet<T>" for unique value storage, and appropriate concurrent collections for multithreaded contexts. Consider using "ImmutableArray<T>" and other immutable collections available from "System.Collections.Immutable" when dealing with multi-threaded scenarios and to reduce allocation. * **Don't Do This:** Use inappropriate collections that lead to performance bottlenecks (e.g., using "LinkedList<T>" for frequent random access). * **Why**: Different collections have different performance characteristics for insertion, deletion, retrieval, and iteration. **Example:** """csharp // Good: Using Dictionary for efficient key-value lookup Dictionary<string, int> ages = new Dictionary<string, int>(); ages.Add("Alice", 30); int aliceAge = ages["Alice"]; // Fast lookup //Good: Immutable Array used to manage a value in a multi-threaded environment. using System.Collections.Immutable; public class DataProcessor { private ImmutableArray<string> _data; public DataProcessor(string[] initialData) { _data = initialData.ToImmutableArray(); } public void AddData(string newData) { ImmutableInterlocked.Update(ref _data, oldData => oldData.Add(newData)); } public ImmutableArray<string> GetData() { return _data; } } //Bad: Accessing values by index on a LinkedList. LinkedList<string> list = new LinkedList<string>(); list.AddLast("one"); list.AddLast("two"); string value = list.ElementAt(1); // Very slow, O(n) """ ### 2.8. Memory Management * **Do This**: Be mindful of memory allocations, especially in loops and performance-critical sections. Use object pooling for frequently created and destroyed objects. Utilize "ArrayPool<T>" to minimize memory allocations for arrays. Explore "Memory<T>" and "Span<T>" for efficient memory access and manipulation. * **Don't Do This**: Create excessive temporary objects, leading to increased garbage collection overhead. * **Why**: Reducing memory allocations improves performance and reduces the load on the garbage collector. **Example:** """csharp // Good: Using ArrayPool using System.Buffers; public class ArrayPoolExample { public void ProcessData() { int[] array = ArrayPool<int>.Shared.Rent(1024); // Rent array from pool try { // Use array for (int i = 0; i < array.Length; i++) { array[i] = i; } } finally { ArrayPool<int>.Shared.Return(array); // Return array to pool } } } //Good: Using Memory<T> and Span<T> for processing data. public static class SpanExample { public static void ProcessBytes(Memory<byte> memory) { Span<byte> bytes = memory.Span; for (int i = 0; i < bytes.Length; i++) { bytes[i] = (byte)(bytes[i] * 2); // Example operation } } } """ ### 2.9. Regular Expressions * **Do This**: Use regular expressions efficiently. Compile regular expressions for reuse, and avoid complex patterns when simpler alternatives are available. * **Don't Do This**: Use overly complex regular expressions or create them repeatedly. This can lead to performance bottlenecks. * **Why**: Regular expression matching can be computationally expensive, especially for complex patterns. **Example:** """csharp // Good: Compiled regular expression using System.Text.RegularExpressions; public class RegexExample { private static readonly Regex MyRegex = new Regex(@"\d+", RegexOptions.Compiled); public bool IsMatch(string input) { return MyRegex.IsMatch(input); // Use compiled regex } } """ ## 3. Concurrency and Parallelism ### 3.1. Thread Pool * **Do This**: Utilize the thread pool for short-lived, CPU-bound operations. Avoid queueing long-running or blocking tasks to the thread pool. * **Don't Do This**: Starve the thread pool by queueing too many long-running tasks. This can lead to performance degradation. * **Why**: The thread pool manages a set of threads to execute tasks efficiently. **Example:** """csharp // Good: Using thread pool using System.Threading.Tasks; public class ThreadPoolExample { public void ProcessData(int data) { Task.Run(() => { // Process data on thread pool Console.WriteLine($"Processing {data} on thread {Thread.CurrentThread.ManagedThreadId}"); }); } } """ ### 3.2. Task Parallel Library (TPL) * **Do This**: Use TPL ("Task", "Parallel") for parallelizing CPU-bound operations. Use "async"/"await" for I/O-bound operations. Be mindful of thread synchronization issues when accessing shared resources. * **Don't Do This**: Overuse parallelism or introduce unnecessary complexity with TPL. Misuse of TPL can lead to deadlocks and race conditions. * **Why**: TPL simplifies parallel programming and allows leveraging multi-core processors. **Example:** """csharp // Good: Using Parallel.For public void ProcessDataParallel(int[] data) { Parallel.For(0, data.Length, i => { // Process data[i] in parallel data[i] = data[i] * 2; Console.WriteLine($"Processing {data[i]} on thread {Thread.CurrentThread.ManagedThreadId}"); }); } """ ### 3.3. Lock Contention * **Do This**: Minimize lock contention by using fine-grained locks or lock-free data structures. Consider using "ConcurrentDictionary<TKey, TValue>" and other concurrent collections. * **Don't Do This**: Use coarse-grained locks that serialize access to large sections of code. This can significantly reduce concurrency. * **Why**: Lock contention occurs when multiple threads try to acquire the same lock simultaneously, leading to performance bottlenecks. **Example:** """csharp // Good: Using ConcurrentDictionary using System.Collections.Concurrent; public class ConcurrentDictionaryExample { private readonly ConcurrentDictionary<string, int> _counts = new ConcurrentDictionary<string, int>(); public void IncrementCount(string key) { _counts.AddOrUpdate(key, 1, (k, v) => v + 1); } public int GetCount(string key) { _counts.TryGetValue(key, out int count); return count; } } """ ## 4. Data Access ### 4.1. Entity Framework Core (EF Core) * **Do This**: Use EF Core efficiently by minimizing round trips to the database. Use projections ("Select") to retrieve only the necessary columns. Use compiled queries for frequently executed queries. Use "AsNoTracking()" when change tracking is not required. * **Don't Do This**: Retrieve entire entities when only a few properties are needed. Perform unnecessary database round trips. Neglect to use compiled queries. * **Why**: Reducing database round trips improves performance and reduces database load. **Example:** """csharp // Good: Using projection to retrieve only necessary columns using Microsoft.EntityFrameworkCore; public class ProductContext : DbContext { public DbSet<Product> Products { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer("YourConnectionString"); } } public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } } public class DataAccessExample { public string GetProductName(int productId) { using ProductContext context = new ProductContext(); return context.Products .Where(p => p.Id == productId) .Select(p => p.Name) .AsNoTracking() .FirstOrDefault(); } } // Good: Compiled Queries public class DataAccessExample { /* Define a compiled query using EF.CompileQuery */ private static readonly Func<ProductContext, int, Product> _getProductById = EF.CompileQuery((ProductContext context, int productId) => context.Products .FirstOrDefault(e => e.Id == productId)); public Product GetProductById(int productId) { using (var context = new ProductContext()) { /* Call the compiled query, passing in the context */ Product product = _getProductById(context, productId); return product; } } } """ ### 4.2. Dapper * **Do This:** Consider using Dapper, a micro-ORM, for simpler and faster data access, especially for read-only scenarios or when full ORM features are not needed. * **Don't Do This:** Use Dapper when a full ORM like EF Core is required for complex object mapping and change tracking. * **Why:** Dapper offers a lightweight alternative to full ORMs, providing faster query execution and reduced overhead. **Example:** """csharp // Good: Using Dapper using Dapper; using System.Data.SqlClient; public class DapperExample { public Product GetProduct(int productId) { using SqlConnection connection = new SqlConnection("YourConnectionString"); connection.Open(); return connection.QueryFirstOrDefault<Product>("SELECT * FROM Products WHERE Id = @Id", new { Id = productId }); } } """ ### 4.3. Connection Pooling * **Do This**: Utilize connection pooling to reuse database connections and reduce connection overhead. Ensure connections are properly closed and disposed of. * **Don't Do This**: Create new database connections for every operation. This results in significant performance overhead. * **Why**: Connection pooling maintains a pool of open connections that can be reused, avoiding the cost of repeatedly opening and closing connections. ## 5. Monitoring and Profiling ### 5.1. Performance Counters * **Do This**: Use performance counters to monitor resource usage (e.g., CPU, memory, disk I/O) of the application. * **Don't Do This**: Neglect to monitor application performance. This makes it difficult to identify performance bottlenecks. * **Why**: Performance counters provide valuable insights into application resource utilization. ### 5.2. Profiling Tools * **Do This**: Use profiling tools (e.g., Visual Studio Profiler, dotTrace, PerfView) to identify performance bottlenecks and optimize code execution. * **Don't Do This**: Guess at performance issues without profiling. This can lead to wasted effort and ineffective optimizations. * **Why**: Profiling tools provide detailed information about code execution, helping to pinpoint performance bottlenecks. ### 5.3. Logging * **Do This**: Implement structured logging to track application events and performance metrics. Use appropriate logging levels (e.g., "Debug", "Information", "Warning", "Error") to avoid excessive logging in production. * **Don't Do This**: Log excessively or store sensitive data in logs. This can impact performance and security. * **Why**: Logging provides valuable information for debugging and performance analysis. ## 6. Specific C# Feature Optimizations ### 6.1 Span\<T> and Memory\<T> * **Do This**: Utilize "Span<T>" and "Memory<T>" for efficient memory access and manipulation, especially when dealing with arrays, strings, and streams. These types provide a way to work with contiguous regions of memory without unnecessary copying. * **Don't Do This**: Rely on traditional array indexing and string manipulation methods in performance-critical sections where "Span<T>" and "Memory<T>" can offer significant performance improvements. * **Why**: "Span<T>" and "Memory<T>" avoid heap allocations and provide direct access to memory, improving performance and reducing garbage collection pressure. **Example:** """csharp // Good: Using Span<T> for processing bytes public static class SpanExample { public static void ProcessBytes(Span<byte> bytes) { for (int i = 0; i < bytes.Length; i++) { bytes[i] = (byte)(bytes[i] * 2); // Example operation } } public static void ExampleUsage(byte[] data) { ProcessBytes(data.AsSpan()); } } """ ### 6.2. Records * **Do This:** Use records, especially "readonly record struct", to represent immutable data structures with value equality, resulting in less code than equivalent classes and value types. * **Don't Do This:** Overuse records where mutable classes might be more appropriate or where value equality is not needed. * **Why:** Records provide concise syntax for creating immutable data structures with value equality, improving code readability and potentially performance due to immutability. **Example:** """csharp // Good: Using readonly record struct for immutable data public readonly record struct Point(int X, int Y); """ ### 6.3. Struct Enumerators and Iterators * **Do This**: To optimise 'foreach' loops, use struct enumerators when dealing with collection types. * **Don't Do This**: Use Enumerable/IEnumerator (reference type) enumerators when a struct enumerator will do, causing unnecessary boxing. **Example:** """csharp // Good: Using struct enumerators when dealing with collection types public readonly struct MyCustomCollection { private readonly int[] _data; public MyCustomCollection(int[] data) => _data = data; public Enumerator GetEnumerator() => new Enumerator(_data); public struct Enumerator //Struct Enumerator, Value Type, no boxing { private readonly int[] _data; private int _index; internal Enumerator(int[] data) { _data = data; _index = -1; } public bool MoveNext() => ++_index < _data.Length; public int Current => _data[_index]; } } """ By following these performance optimization standards, C# developers can create efficient, responsive, and scalable applications that deliver optimal performance and user experience. This document should be treated as a living document and updated periodically to reflect the latest C# features and best practices.
# Core Architecture Standards for C# This document outlines the core architecture standards for C# development, focusing on fundamental architectural patterns, project structure, and organizational principles specific to C#. It targets modern C# approaches, emphasizing maintainability, performance, and security. ## 1. Architectural Patterns ### 1.1. Layered Architecture **Standard:** Implement a layered architecture to separate concerns and improve maintainability. **Do This:** * Define clear layers: Presentation, Application, Domain, Infrastructure. * Establish strict layer dependencies: Presentation -> Application -> Domain -> Infrastructure. * Use interfaces for communication between layers. * Employ Dependency Injection (DI) to decouple layers. **Don't Do This:** * Create cyclic dependencies between layers. * Bypass layers directly, e.g., Presentation accessing Infrastructure directly. * Implement business logic in the Presentation layer. **Why:** Layered architecture enhances code organization, testability, and maintainability. Clear separation of concerns reduces the impact of changes in one layer on other layers. **Code Example:** """csharp // Domain Layer public interface IOrderService { Order GetOrder(Guid orderId); void PlaceOrder(Order order); } public class Order { public Guid Id { get; set; } public DateTime OrderDate { get; set; } public decimal TotalAmount { get; set; } } // Infrastructure Layer public class OrderRepository : IOrderService { public Order GetOrder(Guid orderId) { // Retrieve order from database return new Order { Id = orderId, OrderDate = DateTime.Now, TotalAmount = 100 }; } public void PlaceOrder(Order order) { // Save order to database Console.WriteLine($"Order {order.Id} placed."); } } // Application Layer public class OrderAppService { private readonly IOrderService _orderService; public OrderAppService(IOrderService orderService) { _orderService = orderService; } public Order GetOrder(Guid orderId) { return _orderService.GetOrder(orderId); } public void PlaceOrder(Order order) { _orderService.PlaceOrder(order); } } // Presentation Layer (e.g., ASP.NET Core Controller) [ApiController] [Route("[controller]")] public class OrderController : ControllerBase { private readonly OrderAppService _orderAppService; public OrderController(OrderAppService orderAppService) { _orderAppService = orderAppService; } [HttpGet("{id}")] public ActionResult<Order> Get(Guid id) { var order = _orderAppService.GetOrder(id); if (order == null) { return NotFound(); } return order; } [HttpPost] public IActionResult Post([FromBody] Order order) { _orderAppService.PlaceOrder(order); return Ok(); } } """ **Anti-pattern:** A controller directly instantiating a data access class. ### 1.2. Domain-Driven Design (DDD) **Standard:** Apply DDD principles to model complex business domains effectively. **Do This:** * Define bounded contexts to encapsulate specific business functionalities. * Create a ubiquitous language understood by both developers and domain experts. * Model entities, value objects, aggregates, and domain services effectively. * Use repositories for data access. **Don't Do This:** * Create an anemic domain model with entities only containing properties. * Ignore the advice from domain experts. * Mix domain logic within infrastructure concerns. **Why:** DDD promotes a deeper understanding of the business domain, leading to more robust and maintainable software. **Code Example:** """csharp // Value Object public record Address(string Street, string City, string PostalCode, string Country); // Entity public class Customer { public Guid Id { get; private set; } public string Name { get; private set; } public Address Address { get; private set; } public Customer(Guid id, string name, Address address) { Id = id; Name = name; Address = address; } public void UpdateAddress(Address newAddress) { Address = newAddress; } } // Aggregate Root public class Order { public Guid Id { get; private set; } public Customer Customer { get; private set; } private readonly List<OrderItem> _orderItems = new(); public IReadOnlyCollection<OrderItem> OrderItems => _orderItems.AsReadOnly(); public DateTime OrderDate { get; private set; } public Order(Guid id, Customer customer, DateTime orderDate) { Id = id; Customer = customer; OrderDate = orderDate; } public void AddOrderItem(OrderItem item) { _orderItems.Add(item); } } // Domain Service public interface IOrderDiscountService { decimal CalculateDiscount(Order order); } public class OrderDiscountService : IOrderDiscountService { public decimal CalculateDiscount(Order order) { // Logic to calculate discount based on customer and order details return 0.1m; // 10% discount } } //Repository public interface IOrderRepository { Order? GetById(Guid id); void Add(Order order); void Update(Order order); void Delete(Guid id); } """ **Anti-pattern:** Exposing the internal collection "_orderItems" directly instead of using "AsReadOnly()". ### 1.3. Microservices Architecture **Standard:** If appropriate for the system's complexity, adopt a microservices architecture to enable independent deployment and scaling. **Do This:** * Design services around business capabilities. * Ensure services are independently deployable and scalable. * Implement robust inter-service communication mechanisms (e.g., REST, gRPC, message queues). * Use a decentralized data management approach. **Don't Do This:** * Create tightly coupled services. * Share databases between services. * Fall into the trap of a distributed monolith. **Why:** Microservices allow for greater flexibility, scalability, and resilience, particularly in large and complex systems. **Code Example:** """csharp // Customer Service (ASP.NET Core Web API) [ApiController] [Route("[controller]")] public class CustomerController : ControllerBase { [HttpGet("{id}")] public ActionResult<string> Get(Guid id) { // Retrieve customer information return $"Customer Name: John Doe, ID: {id}"; } } //Order Service (ASP.NET Core Web API) - Communication using HttpClient. public class OrderService{ private readonly HttpClient _httpClient; public OrderService(HttpClient httpClient) { _httpClient = httpClient; } public async Task<string> GetCustomerName(Guid customerId) { var response = await _httpClient.GetAsync($"http://customer-service/customer/{customerId}"); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStringAsync(); } } // Use gRPC for high-performance communication // Define a gRPC service service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); } message HelloRequest { string name = 1; } message HelloReply { string message = 1; } // Implement the gRPC service in C# public class GreeterService : Greeter.GreeterBase { private readonly ILogger<GreeterService> _logger; public GreeterService(ILogger<GreeterService> logger) { _logger = logger; } public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context) { return Task.FromResult(new HelloReply { Message = "Hello " + request.Name }); } } """ **Anti-pattern:** Tightly coupling services by directly referencing each other's data models. ## 2. Project Structure and Organization ### 2.1. Solution Structure **Standard:** Organize the solution into logical projects based on layers (e.g., "MyProject.Presentation", "MyProject.Application", "MyProject.Domain", "MyProject.Infrastructure"). Include separate test projects for efficient unit and integrations tests (e.g. "MyProject.UnitTests"). **Do This:** * Create a separate project for each layer or bounded context. * Use a "src" folder to contain all source code projects. * Use a "tests" folder to contain all test projects. **Don't Do This:** * Place all code in a single project. * Mix different concerns within the same project. **Why:** Logical separation improves maintainability and reduces build times. **Example:** """ MySolution.sln ├── src │ ├── MyProject.Presentation │ │ └── MyProject.Presentation.csproj │ ├── MyProject.Application │ │ └── MyProject.Application.csproj │ ├── MyProject.Domain │ │ └── MyProject.Domain.csproj │ └── MyProject.Infrastructure │ └── MyProject.Infrastructure.csproj └── tests ├── MyProject.UnitTests │ └── MyProject.UnitTests.csproj └── MyProject.IntegrationTests └── MyProject.IntegrationTests.csproj """ ### 2.2. Namespace Conventions **Standard:** Use consistent and descriptive namespaces following the pattern "CompanyName.ProjectName.ModuleName". **Do This:** * Align namespaces with project structure. * Use meaningful names for namespaces. **Don't Do This:** * Use generic namespaces like "Helpers" or "Utilities" without a clear context. * Create namespace hierarchies that don't reflect the code structure. **Why:** Clear namespace conventions improve code discoverability and prevent naming conflicts. **Example:** """csharp namespace MyCompany.ECommerce.Orders { public class OrderService { // ... } } """ ### 2.3. Dependency Injection (DI) Container Configuration **Standard:** Use a DI container (e.g., Autofac, Microsoft.Extensions.DependencyInjection) to manage dependencies. **Do This:** * Configure DI in a dedicated class or method. * Register dependencies using interfaces. * Use constructor injection. **Don't Do This:** * Use the "new" keyword to create dependencies directly within classes. * Hardcode service locations or connection strings. **Why:** DI promotes loose coupling, testability, and maintainability. **Code Example:** """csharp // Program.cs (ASP.NET Core) public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); // Configure DI builder.Services.AddScoped<IOrderService, OrderRepository>(); builder.Services.AddScoped<OrderAppService>(); var app = builder.Build(); // Configure the HTTP request pipeline. app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run(); } } """ **Anti-pattern:** Using a service locator pattern instead of constructor injection. ### 2.4. Asynchronous Programming **Standard:** Utilize "async" and "await" for I/O-bound operations to avoid blocking threads. **Do This:** * Use "async" and "await" for database calls, network requests, and file I/O. * Follow the "Async" suffix convention for asynchronous methods (e.g., "GetOrderAsync"). * Configure "ConfigureAwait(false)" when the synchronization context is not needed. **Don't Do This:** * Use ".Result" or ".Wait()" which can cause deadlocks in ASP.NET applications. * Mix synchronous and asynchronous code without careful consideration. * Ignore exceptions thrown by asynchronous operations. **Why:** Asynchronous programming improves application responsiveness and scalability. **Code Example:** """csharp public interface IOrderService { Task<Order> GetOrderAsync(Guid orderId); } public class OrderRepository : IOrderService { public async Task<Order> GetOrderAsync(Guid orderId) { await Task.Delay(100); //Simulate work return new Order { Id = orderId, OrderDate = DateTime.Now, TotalAmount = 100 }; // Retrieve order from database asynchronously // await dbContext.Orders.FindAsync(orderId).ConfigureAwait(false); } } [ApiController] [Route("[controller]")] public class OrderController : ControllerBase { private readonly IOrderService _orderService; public OrderController(IOrderService orderService) { _orderService = orderService; } [HttpGet("{id}")] public async Task<ActionResult<Order>> Get(Guid id) { var order = await _orderService.GetOrderAsync(id); if (order == null) { return NotFound(); } return order; } } """ **Anti-pattern:** Performing CPU-bound operations using "async" and "await" without offloading to a separate thread. Use "Task.Run" for CPU-bound operations. ## 3. Common Anti-Patterns and Mistakes ### 3.1. God Classes **Issue:** Creating a single class that handles too many responsibilities. **Solution:** Break down the class into smaller, more focused classes following the Single Responsibility Principle (SRP). Apply DDD's bounded contexts to identify logical boundaries. ### 3.2. Feature Envy **Issue:** A method accessing the data of another object more than its own. **Solution:** Move the method to the class where the data resides or create a domain service to handle the operation. ### 3.3. Shotgun Surgery **Issue:** Making a single change requires modifications in multiple classes. **Solution:** Review the design and refactor to encapsulate the change within a single class or module. Look for opportunities to apply the Open/Closed Principle (OCP). ### 3.4. Premature Optimization **Issue:** Optimizing code before identifying actual performance bottlenecks. **Solution:** Measure performance using profiling tools and focus on optimizing the critical paths. "Make it work, make it right, make it fast." ### 3.5. Ignoring Security Concerns **Issue:** Neglecting security best practices, leading to vulnerabilities. **Solution:** Implement security measures such as input validation, output encoding, authentication, authorization, and protection against common attacks like SQL injection and cross-site scripting (XSS). Use static analysis tools to identify potential security vulnerabilities. ## 4. Modern Best Practices ### 4.1. Using Records **Standard:** Use Records for immutable data structures. Available since C# 9.0. **Do This:** * Use records for DTOs, value objects, and other immutable data carriers. * Take advantage of positional syntax for concise declaration. **Don't Do This:** * Use records for entities that require mutability. **Why:** Records provide a concise and safe way to represent immutable data. **Code Example:** """csharp public record Person(string FirstName, string LastName); // Can be instantiated as Person person = new("John", "Doe"); //With C# 10, you can declare record structs: public readonly record struct Point(double X, double Y); """ ### 4.2. Top-Level Statements **Standard:** Use top-level statements for console applications and simple programs. **Do This:** * Simplify the entry point of console applications. **Don't Do This:** * Use top-level statements in complex applications with multiple classes and dependencies. **Why:** Top-level statements reduce boilerplate code. **Code Example:** """csharp // Program.cs Console.WriteLine("Hello, World!"); """ ### 4.3. Target-Typed New Expressions **Standard:** Use target-typed new expressions to simplify object instantiation. **Do This:** * Use "new()" when the type is clear from the context. **Don't Do This:** * Use "new()" when the type is ambiguous. **Why:** Target-typed new expressions reduce redundancy and improve readability. **Code Example:** """csharp List<string> names = new(); // Instead of new List<string>() """ ### 4.4. Global Usings **Standard:** Utilize global usings to reduce redundancy in using directives. Available since C# 10.0. **Do This:** * Add common namespaces to a global using directives file to avoid repeating "using" statements in every file. **Don't Do This:** * Globally import namespaces that are rarely used as this can pollute the scope. **Why:** Global usings improve code clarity and reduce visual clutter. **Code Example:** Create a file (e.g., "GlobalUsings.cs") in your project with the following content: """csharp global using System; global using System.Collections.Generic; global using System.Linq; global using System.Threading.Tasks; """ Now, these namespaces are available in all C# files within the project without needing to explicitly add "using" directives. ### 4.5 Pattern Matching Enhancements **Standard:** Leverage improved pattern matching for concise and expressive code. **Do This:** * Utilize property patterns, positional patterns, and "switch" expressions for complex conditional logic. **Don't Do This:** * Overuse pattern matching for simple conditions, as it can reduce readability. Replace "if-else". **Why:** Pattern matching makes code more readable and maintainable by expressing complex conditions in a declarative way. **Code Example:** """csharp public record Address(string Street, string City, string ZipCode, string Country); public record Person(string FirstName, string LastName, Address Address); public static string GetAddressInfo(Person person) { return person switch { { Address : { Country: "USA", ZipCode: "90210" } } => "Beverly Hills Resident", { Address.Country: "USA" } => "USA Resident", { Address.Country: "Canada" } => "Canadian Resident", _ => "Other Resident" }; } """ ## 5. Technology-Specific Details ### 5.1. ASP.NET Core **Standard:** Follow ASP.NET Core best practices for building web applications. * Use middleware for request processing. * Implement dependency injection (DI) for managing dependencies. * Use logging abstractions for consistent logging. * Apply security best practices (authentication, authorization, data protection). * Leverage minimal APIs based on project need. ### 5.2. Entity Framework Core **Standard:** Adhere to EF Core best practices for data access. * Use asynchronous operations for database interactions. * Avoid loading unnecessary data using projections and filtering. * Implement connection resiliency and retry logic. * Employ code-first migrations for database schema management. * Utilize the repository pattern to abstract data access logic. ### 5.3. C# 12 features C# 12 introduces primary constructors, collection expressions, inline arrays, default lambda expressions, and alias any type. Make sure that the development team understands these new features and uses them accordingly. ## 6. Conclusion These core architecture standards provide a foundation for building robust, maintainable, and scalable C# applications. By following these guidelines, development teams can ensure consistency, improve code quality, and reduce technical debt. Remember to adapt these standards to the specific needs of your project and organization, and regularly review and update them as new technologies and best practices emerge.
# Component Design Standards for C# This document outlines the component design standards for C# development, focusing on creating reusable, maintainable, and scalable components. These standards aim to promote consistency, readability, and adherence to best practices while leveraging modern C# features. ## 1. Principles of Component Design ### 1.1. Single Responsibility Principle (SRP) A component should have one, and only one, reason to change. * **Do This:** Design components that encapsulate a single, well-defined responsibility. * **Don't Do This:** Create "god classes" or components with multiple unrelated responsibilities. **Why SRP Matters:** Improves maintainability, testability, and reduces the impact of changes. A component focused on a single task is easier to understand, modify, and test. **Code Example:** """csharp // Good: Separate concerns public interface IOrderProcessor { void ProcessOrder(Order order); } public interface IOrderValidator { bool ValidateOrder(Order order); } public class OrderProcessor : IOrderProcessor { private readonly IOrderValidator _orderValidator; private readonly IPaymentService _paymentService; public OrderProcessor(IOrderValidator orderValidator, IPaymentService paymentService) { _orderValidator = orderValidator; _paymentService = paymentService; } public void ProcessOrder(Order order) { if (!_orderValidator.ValidateOrder(order)) { throw new InvalidOperationException("Order is invalid."); } _paymentService.ProcessPayment(order.PaymentInfo); // ... other order processing logic } } // Bad: Violates SRP public class OrderService // Contains multiple responsibilities (validation, processing, payment) { public void ProcessOrder(Order order) { if (!IsValidOrder(order)) { // ... validation logic } // ... payment logic // ... order processing logic } private bool IsValidOrder(Order order) { // ... order validation logic return true; // or false } } """ ### 1.2. Open/Closed Principle (OCP) Components should be open for extension but closed for modification. * **Do This:** Use inheritance, interfaces, and composition to extend functionality without altering the original component. * **Don't Do This:** Modify existing component code to add new features, as this can introduce regressions and break existing functionality. **Why OCP Matters:** Enhances code reuse, reduces the risk of introducing bugs when adding new features, and simplifies maintenance. **Code Example:** """csharp // Good: Using an abstract base class public abstract class DiscountCalculator { public abstract decimal CalculateDiscount(decimal price); } public class SummerDiscountCalculator : DiscountCalculator { public override decimal CalculateDiscount(decimal price) { return price * 0.10m; // 10% discount } } public class WinterDiscountCalculator : DiscountCalculator { public override decimal CalculateDiscount(decimal price) { return price * 0.15m; // 15% discount } } // Bad: Modifying existing class public class DiscountCalculator { public decimal CalculateDiscount(decimal price, string season) { if (season == "Summer") { return price * 0.10m; } else if (season == "Winter") { return price * 0.15m; } return price; } } """ ### 1.3. Liskov Substitution Principle (LSP) Subtypes should be substitutable for their base types without altering the correctness of the program. * **Do This:** Ensure that derived classes behave consistently with their base classes. * **Don't Do This:** Override methods in a way that changes the expected behavior or violates the base class contract. **Why LSP Matters:** Guarantees that polymorphism works as expected, promoting reliable code and reducing unexpected errors. **Code Example:** """csharp // Good: Maintaining behavior consistency public abstract class Shape { public abstract int Area(); } public class Rectangle : Shape { public int Width { get; set; } public int Height { get; set; } public override int Area() => Width * Height; } public class Square : Shape { public int Side { get; set; } public override int Area() => Side * Side; } // Bad: Violating the Rectangle/Square relationship (LSP) public class BadRectangle { public virtual int Width { get; set; } public virtual int Height { get; set; } public int Area() => Width * Height; } public class BadSquare : BadRectangle { // Violates LSP - setting width changes height and vice-versa private int _side; public override int Width { get => _side; set { _side = value; Height = value; } } public override int Height { get => _side; set { _side = value; Width = value; } } } """ ### 1.4. Interface Segregation Principle (ISP) Clients should not be forced to depend on methods they do not use. * **Do This:** Create small, specific interfaces that cater to specific client needs. * **Don't Do This:** Create large, monolithic interfaces requiring clients to implement methods they don't need. **Why ISP Matters:** Reduces coupling, improves code clarity and maintainability, and allows for more flexible component design. **Code Example:** """csharp // Good: Segregated Interfaces public interface IPrintable { void Print(); } public interface IScannable { void Scan(); } public class ModernPrinter : IPrintable, IScannable { public void Print() { /* Printing logic */ } public void Scan() { /* Scanning logic */ } } public class SimplePrinter : IPrintable { public void Print() { /* Printing logic */ } } // Bad: Fat Interface public interface IMultiFunctionDevice { void Print(); void Scan(); void Fax(); } """ ### 1.5. Dependency Inversion Principle (DIP) High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions. * **Do This:** Use dependency injection (DI) to inject dependencies through constructor injection, property injection, or method injection. Rely on Interfaces and abstract classes instead of concrete implementations. * **Don't Do This:** Create tightly coupled dependencies between high-level and low-level modules. Use "new" keyword liberally throughout your code. **Why DIP Matters:** Promotes loose coupling, improves testability, and makes code more flexible and reusable. **Code Example:** """csharp // Good: Using Dependency Injection public interface IEmailService { void SendEmail(string to, string subject, string body); } public class EmailService : IEmailService { public void SendEmail(string to, string subject, string body) { // ... email sending logic } } public class NotificationService { private readonly IEmailService _emailService; public NotificationService(IEmailService emailService) // Constructor Injection { _emailService = emailService; } public void SendNotification(string userEmail, string message) { _emailService.SendEmail(userEmail, "Notification", message); } } // Usage (e.g., in a DI container): // services.AddScoped<IEmailService, EmailService>(); // services.AddScoped<NotificationService>(); // Bad: Tight coupling public class BadNotificationService { private readonly EmailService _emailService = new EmailService(); // Tight coupling public void SendNotification(string userEmail, string message) { _emailService.SendEmail(userEmail, "Notification", message); } } """ ## 2. Component Design Patterns ### 2.1. Factory Pattern Use factory patterns to abstract the creation of objects. * **Do This:** Employ factory methods or abstract factories to decouple the creation logic from the client code. * **Don't Do This:** Directly instantiate objects within client code, especially when object creation is complex. **Why Factory Pattern Matters:** Provides flexibility in object creation, simplifies testing, and adheres to the open/closed principle. **Code Example:** """csharp // Abstract Product public abstract class Product { public abstract string Operation(); } // Concrete Products public class ConcreteProductA : Product { public override string Operation() => "ConcreteProductA"; } public class ConcreteProductB : Product { public override string Operation() => "ConcreteProductB"; } // Abstract Factory public abstract class Factory { public abstract Product CreateProduct(); } // Concrete Factories public class ConcreteFactoryA : Factory { public override Product CreateProduct() => new ConcreteProductA(); } public class ConcreteFactoryB : Factory { public override Product CreateProduct() => new ConcreteProductB(); } // Client code public class Client { private readonly Product _product; public Client(Factory factory) { _product = factory.CreateProduct(); } public string Run() => _product.Operation(); } """ ### 2.2. Strategy Pattern Define a family of algorithms, encapsulate each one, and make them interchangeable. * **Do This:** Implement different algorithms as separate classes and use composition to switch between them. * **Don't Do This:** Use conditional statements to select algorithms within a single component. **Why Strategy Pattern Matters:** Promotes flexibility, reduces code duplication, and allows for easy addition of new algorithms. **Code Example:** """csharp // Strategy Interface public interface IPaymentStrategy { void Pay(decimal amount); } // Concrete Strategies public class CreditCardPayment : IPaymentStrategy { private readonly string _cardNumber; private readonly string _expiryDate; public CreditCardPayment(string cardNumber, string expiryDate) { _cardNumber = cardNumber; _expiryDate = expiryDate; } public void Pay(decimal amount) { // ... credit card payment logic Console.WriteLine($"Paid {amount} using Credit Card: {_cardNumber}"); } } public class PayPalPayment : IPaymentStrategy { private readonly string _email; public PayPalPayment(string email) { _email = email; } public void Pay(decimal amount) { // ... PayPal payment logic Console.WriteLine($"Paid {amount} using PayPal: {_email}"); } } // Context public class ShoppingCart { private IPaymentStrategy _paymentStrategy; public void SetPaymentStrategy(IPaymentStrategy paymentStrategy) { _paymentStrategy = paymentStrategy; } public void Checkout(decimal amount) { _paymentStrategy.Pay(amount); } } // Usage // var cart = new ShoppingCart(); // cart.SetPaymentStrategy(new CreditCardPayment("1234-5678-9012-3456", "12/25")); // cart.Checkout(100.00m); """ ### 2.3. Observer Pattern Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. * **Do This:** Use the "IObservable<T>" and "IObserver<T>" interfaces or events and delegates to implement the observer pattern. * **Don't Do This:** Create tight coupling between subjects and observers. **Why Observer Pattern Matters:** Decouples subjects and observers, allows for flexible event handling, and facilitates reactive programming. **Code Example:** """csharp // Subject Interface public interface IObservable { void Subscribe(IObserver observer); void Unsubscribe(IObserver observer); } // Observer Interface public interface IObserver { void Update(string message); } // Concrete Subject public class NewsAgency : IObservable { private readonly List<IObserver> _observers = new List<IObserver>(); private string _news; public void Subscribe(IObserver observer) { _observers.Add(observer); } public void Unsubscribe(IObserver observer) { _observers.Remove(observer); } public string News { get => _news; set { _news = value; NotifyObservers(); } } private void NotifyObservers() { foreach (var observer in _observers) { observer.Update(_news); } } } // Concrete Observers public class NewsReader : IObserver { private readonly string _name; public NewsReader(string name) { _name = name; } public void Update(string message) { Console.WriteLine($"{_name} received news: {message}"); } } """ ## 3. Modern C# Features for Component Design ### 3.1. Records Use records for immutable data transfer objects (DTOs) and value objects. * **Do This:** Define records with properties, deconstruction, and equality implementations. * **Don't Do This:** Use classes for simple data holders, as records provide more concise syntax and built-in immutability. **Why Records Matter:** Simpler syntax, improved immutability, and enhanced data integrity. **Code Example:** """csharp public record Person(string FirstName, string LastName, int Age); // Usage Person person = new("John", "Doe", 30); Console.WriteLine(person); // Output: Person { FirstName = John, LastName = Doe, Age = 30 } // Deconstruction var (firstName, lastName, age) = person; Console.WriteLine($"First Name: {firstName}, Last Name: {lastName}, Age: {age}"); """ ### 3.2. Nullable Reference Types Enable nullable reference types to avoid null reference exceptions. * **Do This:** Annotate reference types with "?" to indicate they can be null. Handle nullable values properly with null checks or the null-conditional operator ("?."). * **Don't Do This:** Ignore nullable warnings, as they indicate potential null reference issues. **Why Nullable Reference Types Matter:** Helps prevent null reference exceptions, improves code safety, and enhances code analysis. **Code Example:** """csharp #nullable enable public class Address { public string? Street { get; set; } // Street can be null public string City { get; set; } = ""; // City cannot be null } public class Person { public string FirstName { get; set; } = ""; public Address? Address { get; set; } // Address can be null public string GetFullAddress() { return Address?.Street + ", " + Address?.City; // Using null-conditional operator } } #nullable disable """ ### 3.3. Asynchronous Programming with "async" and "await" Use asynchronous programming for I/O-bound and CPU-bound operations. * **Do This:** Use "async" and "await" for long-running operations to prevent UI freezing and improve responsiveness. * **Don't Do This:** Block threads with ".Result" or ".Wait()" when awaiting tasks. **Why Async/Await Matters:** Improves application performance, enhances responsiveness, and provides a cleaner way to handle asynchronous operations. **Code Example:** """csharp public async Task<string> DownloadDataAsync(string url) { using HttpClient client = new HttpClient(); HttpResponseMessage response = await client.GetAsync(url); response.EnsureSuccessStatusCode(); string content = await response.Content.ReadAsStringAsync(); return content; } // Proper usage: // string data = await DownloadDataAsync("https://example.com"); // Anti-pattern, blocking the thread: // string data = DownloadDataAsync("https://example.com").Result; """ ### 3.4. Dependency Injection (DI) with .NET's Built-in Container Utilize .NET's built-in DI container for managing component dependencies. * **Do This:** Register services with lifetime scopes (singleton, scoped, transient) and inject dependencies through constructor injection. Use interfaces for service contracts and provide concrete implementations. * **Don't Do This:** Manually create and manage dependencies within components, as this leads to tight coupling and poor testability. **Why DI Matters:** Promotes loose coupling, improves testability, and simplifies component management. **Code Example:** """csharp // Startup.cs or Program.cs in .NET 6+ public void ConfigureServices(IServiceCollection services) { services.AddTransient<IMyService, MyService>(); // Transient lifetime services.AddScoped<IMyScopedService, MyScopedService>(); // Scoped lifetime services.AddSingleton<IMySingletonService, MySingletonService>(); // Singleton lifetime services.AddControllers(); } public class MyComponent { private readonly IMyService _myService; public MyComponent(IMyService myService) { _myService = myService; } public void DoSomething() { _myService.PerformAction(); } } """ ### 3.5. Top-Level Statements and Implicit Usings (C# 9+) Use top-level statements to simplify console applications and scripts. Utilize implicit usings to reduce boilerplate code. * **Do This:** Enable implicit usings in your project file ("<ImplicitUsings>enable</ImplicitUsings>"). Use top-level statements for simple console apps and scripts. * **Don't Do This:** Rely on verbose boilerplate code when top-level statements and implicit usings can simplify your code. **Why Top-Level Statements and Implicit Usings Matter:** Reduces boilerplate, improves code readability, and simplifies project setup. **Code Example:** """csharp // Program.cs (Top-level statements) // <ImplicitUsings>enable</ImplicitUsings> in .csproj Console.WriteLine("Hello, World!"); // You can directly use Console, HttpClient, etc. without explicit usings. // Sample .csproj file: // <Project Sdk="Microsoft.NET.Sdk.Web"> // <PropertyGroup> // <TargetFramework>net7.0</TargetFramework> // <Nullable>enable</Nullable> // <ImplicitUsings>enable</ImplicitUsings> // </PropertyGroup> // </Project> """ ## 4. Component Design Best Practices ### 4.1. Favor Composition Over Inheritance Prefer composition over inheritance to achieve code reuse and flexibility. * **Do This:** Create components that compose behaviors from other smaller components. * **Don't Do This:** Create deep inheritance hierarchies, as they can lead to the fragile base class problem and limit flexibility. **Why Composition Matters:** Reduces coupling, enhances flexibility, and simplifies component evolution. **Code Example:** """csharp // Good: Using Composition public interface IFlyable { void Fly(); } public interface ISwimmable { void Swim(); } public class CanFly : IFlyable { public void Fly() { Console.WriteLine("Flying..."); } } public class CanSwim : ISwimmable { public void Swim() { Console.WriteLine("Swimming..."); } } public class Duck { private readonly IFlyable _flyable; private readonly ISwimmable _swimmable; public Duck(IFlyable flyable, ISwimmable swimmable) { _flyable = flyable; _swimmable = swimmable; } public void Fly() => _flyable.Fly(); public void Swim() => _swimmable.Swim(); } // Bad: Using Inheritance (less flexible) public class Animal { public virtual void Move() { } } public class Bird : Animal { public override void Move() { Console.WriteLine("Flying..."); } } """ ### 4.2. Implement Defensive Programming Write code with built-in checks to prevent errors and handle unexpected conditions gracefully. * **Do This:** Validate input parameters, handle exceptions appropriately, and use assertions to verify assumptions. * **Don't Do This:** Assume that input is always valid or ignore potential error conditions. **Why Defensive Programming Matters:** Improves code reliability, reduces the risk of runtime errors, and enhances application security. **Code Example:** """csharp public static int Divide(int numerator, int denominator) { if (denominator == 0) { throw new ArgumentException("Denominator cannot be zero."); } int result = numerator / denominator; return result; } public static void ProcessData(string data) { if (string.IsNullOrEmpty(data)) { throw new ArgumentException("Data cannot be null or empty."); } try { // ... processing logic } catch (Exception ex) { // Log the exception or take appropriate action Console.WriteLine($"An error occurred: {ex.Message}"); } } """ ### 4.3. Use Code Contracts Use contracts to define preconditions, postconditions, and object invariants. * **Do This:** Define contracts using "Contract.Requires", "Contract.Ensures", and "Contract.Invariant" from the "System.Diagnostics.Contracts" namespace. * **Don't Do This:** Rely solely on comments to document contracts, as comments are not enforced at runtime. **Why Code Contracts Matter:** Improves code reliability, enables static analysis, and enhances runtime validation. **Code Example:** """csharp using System.Diagnostics.Contracts; public class Calculator { public int Add(int x, int y) { Contract.Requires(x >= 0); Contract.Requires(y >= 0); Contract.Ensures(Contract.Result<int>() >= x); Contract.Ensures(Contract.Result<int>() >= y); return x + y; } } """ ### 4.4. Exception Handling Handle exceptions correctly to prevent application crashes and provide informative error messages. * **Do This:** Catch specific exception types, log exceptions, and re-throw exceptions when necessary (e.g., after logging or performing cleanup). Use custom exceptions derived from "System.Exception" or "System.ApplicationException" where appropriate. * **Don't Do This:** Catch generic exceptions ("Exception"), as this can hide important error information or swallow exceptions unnecessarily. Swallow exceptions without logging or handling them. **Why Exception Handling Matters:** Improves application stability, provides useful error information, and allows for graceful recovery from errors. **Code Example:** """csharp public class CustomException : Exception { public CustomException(string message) : base(message) {} public CustomException(string message,Exception innerException) : base(message, innerException) {} } public void ProcessData(string data) { try { // ... processing logic if (string.IsNullOrEmpty(data)) { throw new CustomException("Data is invalid"); } } catch (ArgumentNullException ex) { // Log the exception Console.Error.WriteLine($"ArgumentNullException: {ex.Message}"); // Re-throw the exception, or handle it gracefully throw; } catch (CustomException ex) { Console.Error.WriteLine($"CustomException: {ex.Message}"); } catch (Exception ex) //Catch more specific exceptions before the generic Exception { // Log the exception Console.Error.WriteLine($"An unexpected error occurred: {ex.Message}"); // Consider re-throwing the exception or handling it gracefully. } } """ ### 4.5. Design for Testability Create components that are easy to test in isolation. * **Do This:** Use dependency injection to inject mock dependencies, follow SRP, and avoid static dependencies. * **Don't Do This:** Create tightly coupled components that are difficult to isolate and test. **Why Testability Matters:** Improves code quality, reduces the risk of bugs, and simplifies refactoring. **Code Example:** """csharp // Designed for testability public interface IDataService { string GetData(); } public class DataService : IDataService { public string GetData() { // ... data retrieval logic return "Real Data"; } } public class MyComponent { private readonly IDataService _dataService; public MyComponent(IDataService dataService) { _dataService = dataService; } public string ProcessData() { string data = _dataService.GetData(); // ... processing logic return data.ToUpper(); } } // Test Example (using Moq) // var mockDataService = new Mock<IDataService>(); // mockDataService.Setup(x => x.GetData()).Returns("Mock Data"); // var component = new MyComponent(mockDataService.Object); // Assert.AreEqual("MOCK DATA", component.ProcessData()); """ By adhering to these component design standards, C# developers can create robust, maintainable, and scalable applications that meet the demands of modern software development. These guidelines promote best practices in component design by emphasizing loose coupling, high cohesion, and the strategic use of modern C# features.
# State Management Standards for C# This document outlines the recommended standards and best practices for managing state in C# applications. Effective state management is crucial for building maintainable, scalable, and performant applications. These guidelines cover various approaches, patterns, and techniques, focusing on modern C# features and the .NET ecosystem. ## 1. Introduction to State Management in C# State management involves handling the data an application uses and how it changes over time. In C#, this can range from simple variable assignments within a method to complex architectures for managing application-wide data across multiple layers and components. Incorrect state management can lead to bugs that are hard to track, performance bottlenecks, and difficulties in maintaining the application over time. ### 1.1 Key Principles of State Management * **Single Source of Truth:** Ensure that each piece of data has one authoritative source to prevent inconsistencies. * **Immutability:** Favor immutable data structures to simplify reasoning about state changes and prevent unintended side effects. When state needs to change, create a new instance reflecting the changes. * **Explicit State Transitions:** Make state transitions explicit and predictable to improve debugging and maintainability. * **Separation of Concerns:** Decouple state management logic from UI and business logic to improve testability and reusability. * **Reactivity:** Utilize Reactive programming to propagate state changes to the UI and application using observable streams. ## 2. Local State Management Local state refers to data that is confined to a specific scope, such as a method or a class instance. ### 2.1 Variables and Fields #### Standard 1: Declare variables as close as possible to their usage. * **Do This:** """csharp public void ProcessData(List<string> items) { // ... other code ... if (items != null && items.Any()) { int processedCount = 0; // Declare variable close to usage foreach (var item in items) { // ... process item ... processedCount++; } Console.WriteLine($"Processed {processedCount} items."); } } """ * **Don't Do This:** """csharp public void ProcessData(List<string> items) { int processedCount; // Declared too early // ... other code ... if (items != null && items.Any()) { processedCount = 0; foreach (var item in items) { // ... process item ... processedCount++; } Console.WriteLine($"Processed {processedCount} items."); } } """ * **Why:** Reduces cognitive load, improves readability, and minimizes the risk of using uninitialized variables. #### Standard 2: Use "readonly" for fields that should not change after initialization. * **Do This:** """csharp public class Configuration { public readonly string ApiEndpoint; public Configuration(string apiEndpoint) { ApiEndpoint = apiEndpoint; } } """ * **Don't Do This:** """csharp public class Configuration { public string ApiEndpoint { get; set; } // Mutable! public Configuration(string apiEndpoint) { ApiEndpoint = apiEndpoint; } } """ * **Why:** Enforces immutability, making the code safer and easier to reason about. #### Standard 3: Prefer immutable data structures locally. * **Do This:** C# 9 introduced "init" only setters which are ideal for initializing immutable objects. """csharp public record Person(string FirstName, string LastName) { public string FullName { get; init; } = $"{FirstName} {LastName}"; } public void Example() { var person = new Person("John", "Doe") { FullName = "Johnny Doe"}; //Use "init" to initialize } """ * **Don't Do This:** """csharp public class Person { public string FirstName { get; set; } public string LastName { get; set; } } """ * **Why:** Simplifies debugging by preventing accidental modifications to shared state within a specific scope. ### 2.2 Caching #### Standard 4: Use "MemoryCache" for simple, in-memory caching needs. * **Do This:** """csharp using Microsoft.Extensions.Caching.Memory; public class DataService { private readonly IMemoryCache _cache; public DataService(IMemoryCache cache) { _cache = cache; } public async Task<string> GetDataAsync(string key) { return await _cache.GetOrCreateAsync(key, async entry => { entry.SlidingExpiration = TimeSpan.FromMinutes(10); entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1); return await FetchDataFromSourceAsync(key); }); } private async Task<string> FetchDataFromSourceAsync(string key) { // Simulate fetching data await Task.Delay(100); return $"Data for {key} fetched from source"; } } """ * **Why:** Provides a simple and efficient way to cache data in memory, reducing latency and improving performance. #### Standard 5: Properly invalidate cache entries when the underlying data changes. * **Do This:** """csharp public async Task UpdateDataAsync(string key, string newData) { // Update the underlying data source. // ... // Invalidate the cache entry. _cache.Remove(key); } """ * **Why:** Avoids serving stale data to users, ensuring data consistency. ## 3. Application State Management Application state refers to data that needs to be shared across multiple components of an application, often spanning different layers or modules. ### 3.1 Dependency Injection (DI) #### Standard 6: Use DI to manage the lifecycle of stateful objects. * **Do This:** """csharp using Microsoft.Extensions.DependencyInjection; public interface IDataService { Task<string> GetDataAsync(string key); } public class DataService : IDataService { private readonly IMemoryCache _cache; public DataService(IMemoryCache cache) { _cache = cache; } public async Task<string> GetDataAsync(string key) { // Retrieve or create data with MemoryCache return await _cache.GetOrCreateAsync(key, async entry => { entry.SlidingExpiration = TimeSpan.FromMinutes(10); entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1); return await FetchDataFromSourceAsync(key); }); } private async Task<string> FetchDataFromSourceAsync(string key) { // Simulate fetching data await Task.Delay(100); return $"Data for {key} fetched from source"; } } public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMemoryCache(); // Configures MemoryCache implementation services.AddScoped<IDataService, DataService>(); } } """ * **Why:** Enables loose coupling, improves testability, and simplifies the management of shared state. #### Standard 7: Choose appropriate DI scopes based on the state's lifecycle requirements (Singleton, Scoped, Transient). * **Singleton:** Use for application-wide settings, configuration, and read-only data. * **Scoped:** Use for data that is specific to a user session or request. * **Transient:** Use for objects that should be recreated every time they are requested. ### 3.2 Options Pattern #### Standard 8: Use the Options pattern to configure components with external settings. * **Do This:** """csharp using Microsoft.Extensions.Options; public class AppSettings { public string ApiUrl { get; set; } public int Timeout { get; set; } } public class MyService { private readonly AppSettings _settings; public MyService(IOptions<AppSettings> settings) { _settings = settings.Value; } public void DoSomething() { Console.WriteLine($"API URL: {_settings.ApiUrl}, Timeout: {_settings.Timeout}"); } } public class Startup { public void ConfigureServices(IServiceCollection services) { services.Configure<AppSettings>(configuration.GetSection("AppSettings")); services.AddTransient<MyService>(); } } """ * **Why:** Provides a type-safe way to access configuration settings, simplifies testing and reduces the risk of errors. Supports hot reloading of configuration in .NET 6+. ### 3.3 Centralized State Management with Redux/Flux-like Patterns For complex applications with intricate global state, consider patterns like Redux or Flux. While not a direct implementation, libraries like Fluxor provide a C# adaptation of these patterns. These patterns center around a unidirectional data flow and a central store to manage application state. #### Standard 9: Implement a Redux/Flux-like pattern for complex state management. * **Do This (using Fluxor):** """csharp // State public class TodoState { public ImmutableArray<string> Todos { get; init; } public bool IsLoading { get; init; } } // Feature public class TodoFeature : Feature<TodoState> { public override string GetName() => "Todo"; protected override TodoState GetInitialState() => new TodoState { Todos = ImmutableArray<string>.Empty, IsLoading = false }; } // Actions public record AddTodoAction(string Todo); public record LoadTodosAction(); public record LoadTodosResultAction(ImmutableArray<string> Todos); // Reducers public static class TodoReducers { [ReducerMethod] public static TodoState ReduceAddTodoAction(TodoState state, AddTodoAction action) => state with { Todos = state.Todos.Add(action.Todo) }; // Use "with" to create a new instance [ReducerMethod] public static TodoState ReduceLoadTodosAction(TodoState state, LoadTodosAction action) => state with { IsLoading = true }; [ReducerMethod] public static TodoState ReduceLoadTodosResultAction(TodoState state, LoadTodosResultAction action) => state with { Todos = action.Todos, IsLoading = false }; } // Effects - Side effects public class TodoEffects { private readonly HttpClient _http; public TodoEffects(HttpClient http) { _http = http; } [EffectMethod] public async Task HandleLoadTodosAction(LoadTodosAction action, IDispatcher dispatcher) { var todos = await _http.GetFromJsonAsync<string[]>("/api/todos"); dispatcher.Dispatch(new LoadTodosResultAction(todos.ToImmutableArray())); } } // Component example public class TodoComponent : ComponentBase { [Inject] private IState<TodoState> TodoState { get; set; } [Inject] private IDispatcher Dispatcher { get; set; } protected override void OnInitialized() { base.OnInitialized(); Dispatcher.Dispatch(new LoadTodosAction()); } private void AddTodo(string todo) { Dispatcher.Dispatch(new AddTodoAction(todo)); } // UI logic to display todos } // In your Program.cs or main bootstrapping: public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }) .ConfigureServices((hostContext, services) => { services.AddFluxor(options => { options.ScanAssemblies(typeof(Program).Assembly); options.UseReduxDevTools(); // Optional Redux DevTools integration. Requires browser extension. }); services.AddScoped<HttpClient>(); }); """ * **Why:** * Provides a predictable and centralized approach to managing application state. * Simplifies debugging and testing by enforcing a unidirectional data flow. * Enables time-travel debugging with tools like Redux DevTools. * "with" keyword (records) promotes immutable state updates. * Effects handle side effects (async calls, etc.) separate from reducers which makes code easier to maintain and more testable. #### Standard 10: Avoid directly modifying the state store. Only modify state through actions and reducers. * **Why:** Ensures that all state changes are predictable and traceable, improving debugging and maintainability. Violating this principle negates one of the main benefits of the pattern. ### 3.4 Reactive Programming with Rx.NET Rx.NET (Reactive Extensions for .NET) enables handling asynchronous data streams and change propagation in a declarative and composable manner. #### Standard 11: Use "IObservable<T>" for propagating state changes asynchronously. * **Do This:** """csharp using System; using System.Reactive.Subjects; public class DataService { private readonly Subject<string> _dataStream = new Subject<string>(); public IObservable<string> DataStream => _dataStream; public void UpdateData(string newData) { _dataStream.OnNext(newData); } } public class UIComponent { private readonly DataService _dataService; private IDisposable _subscription; public UIComponent(DataService dataService) { _dataService = dataService; } public void SubscribeToDataChanges() { _subscription = _dataService.DataStream.Subscribe(data => { // Update UI with new data Console.WriteLine($"UI received: {data}"); }); } public void Unsubscribe() { _subscription?.Dispose(); } } """ * **Why:** * Enables reactive updates in the UI or other parts of the application. * Provides a clean and efficient way to handle asynchronous data streams. * Allows you to compose complex data transformations and filtering using LINQ-like operators. #### Standard 12: Dispose of "IDisposable" subscriptions to prevent memory leaks. * **Why:** Essential to avoid holding references to objects that are no longer needed. Common Anti-Pattern: Ignoring Resources: Not disposing of IDisposable resources leads to resource leaks and eventual application instability. Use "using" statements or try-finally blocks. ## 4. Data Persistence How to persist any state outside of an app's lifecycle ### 4.1 Entity Framework Core (EF Core) #### Standard 13: Use EF Core for database access, employing best practices for performance and security. * **Do This:** """csharp using Microsoft.EntityFrameworkCore; public class BloggingContext : DbContext { public BloggingContext(DbContextOptions<BloggingContext> options) : base(options) { } public DbSet<Blog> Blogs { get; set; } public DbSet<Post> Posts { get; set; } } public class Blog { public int BlogId { get; set; } public string Url { get; set; } public List<Post> Posts { get; set; } } public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public int BlogId { get; set; } public Blog Blog { get; set; } } public class BlogService { private readonly BloggingContext _context; public BlogService(BloggingContext context) { _context = context; } public async Task<List<Blog>> GetBlogsAsync() { return await _context.Blogs.ToListAsync(); } public async Task AddBlogAsync(Blog blog) { _context.Blogs.Add(blog); await _context.SaveChangesAsync(); } } """ * **Why:** Provides an ORM for interacting with databases, simplifying data access and management. #### Standard 14: Use asynchronous operations ("async"/"await") for all database interactions to avoid blocking the UI or other threads. * **Don't Do This:** """csharp public List<Blog> GetBlogs() //Synchronous, can block the thread { return _context.Blogs.ToList(); } """ #### Standard 15: Protect against SQL injection by using parameterized queries or EF Core's built-in mechanisms. * **Do This:** """csharp var searchTerm = "example"; var blogs = _context.Blogs.Where(b => EF.Functions.Like(b.Url, $"%{searchTerm}%")).ToList(); """ * **Don't Do This:** """csharp var searchTerm = "example"; var blogs = _context.Blogs.FromSqlRaw($"SELECT * FROM Blogs WHERE Url LIKE '%{searchTerm}%'").ToList(); //Vulnerable! User input not sanitized. """ #### Standard 16: Configure appropriate database contexts scopes (Scoped is best practice for web apps). ### 4.2 Using Distributed Caching (Redis) For applications that need to share state across multiple servers, distributed caching is an important technique. #### Standard 17: Use Redis (or another distributed cache) for session state. * **Do This:** """csharp using Microsoft.Extensions.Caching.Distributed; using System.Text.Json; public class SessionService { private readonly IDistributedCache _cache; public SessionService(IDistributedCache cache) { _cache = cache; } public async Task SetSessionDataAsync<T>(string key, T data) { var options = new DistributedCacheEntryOptions() .SetSlidingExpiration(TimeSpan.FromMinutes(20)); var jsonData = JsonSerializer.Serialize(data); await _cache.SetStringAsync(key, jsonData, options); } public async Task<T> GetSessionDataAsync<T>(string key) { var jsonData = await _cache.GetStringAsync(key); if (jsonData == null) { return default; } return JsonSerializer.Deserialize<T>(jsonData); } public async Task RemoveSessionDataAsync(string key) { await _cache.RemoveAsync(key); } } // Configure Redis in Startup.cs public void ConfigureServices(IServiceCollection services) { services.AddStackExchangeRedisCache(options => { options.Configuration = Configuration.GetConnectionString("Redis"); // Connection string options.InstanceName = "MyAppName:"; // Optional prefix }); services.AddScoped<SessionService>(); } """ * **Why:** Allows you to share data across multiple instances of your application, which is necessary for scaling out. ## 5. Conclusion Following these standards will enable you to write clearer, more performant, and more maintainable C# code, particularly when dealing with application state management. These are general guidelines, and the specifics should be refined based on the particular needs of each project. Furthermore, remember that constantly evolving C# language and technologies necessitate continuous learning and adjustment of these coding standards.
# Testing Methodologies Standards for C# This document outlines the testing methodologies standards to be followed when developing C# applications. It covers unit, integration, and end-to-end testing strategies, along with best practices, anti-patterns, and practical C# examples. ## 1. Introduction to Testing in C# Effective testing is crucial for ensuring the reliability, maintainability, and correctness of C# applications. A well-structured testing strategy helps identify defects early in the development lifecycle, reducing overall costs and improving software quality. ### 1.1. Types of Tests * **Unit Tests:** Test individual components or methods in isolation. * **Integration Tests:** Test the interactions between multiple components or modules. * **End-to-End (E2E) Tests:** Test the entire application flow from start to finish, simulating real user scenarios. ### 1.2. Why Testing Matters * **Defect Prevention:** Early detection of issues reduces costs. * **Maintainability:** Tests act as living documentation, facilitating refactoring and code changes. * **Reliability:** Thorough testing ensures the application functions as expected under various conditions. * **Confidence:** Developers can confidently deploy and maintain applications knowing they are well-tested. ## 2. Unit Testing Standards Unit testing involves testing individual components in isolation. This requires mocking dependencies and ensuring that each unit of code behaves as expected. ### 2.1. Principles of Unit Testing * **Focus on Individual Units:** Test one method or class at a time. * **Isolation:** Use mocks and stubs to isolate the unit under test. * **Fast Execution:** Unit tests should run quickly to provide immediate feedback. * **Repeatable:** Results should be consistent every time the tests are run. * **Automated:** Tests should be automated as part of the build process. ### 2.2. Do This * **Use a Unit Testing Framework:** Utilize frameworks like xUnit, NUnit, or MSTest. * **Follow the AAA Pattern:** Arrange, Act, Assert. * **Write Clear and Concise Tests:** Each test should have a specific purpose. * **Use Meaningful Test Names:** Test names should describe what is being tested. * **Test All Scenarios:** Include positive, negative, and boundary conditions. * **Use Mocking Frameworks:** Use libraries like Moq or NSubstitute for mocking dependencies. * **Strive for High Code Coverage:** Aim for 80-100% coverage in critical modules. ### 2.3. Don't Do This * **Test Multiple Units in a Single Test:** Each test should focus on one unit. * **Depend on External Resources:** Avoid using databases or network resources in unit tests directly. Use mocks instead. * **Write Tests That are Difficult to Understand:** Tests should be clear and self-explanatory. * **Ignore Test Failures:** Investigate and fix failing tests immediately. * **Mock Everything:** Only mock dependencies that are necessary for isolation. ### 2.4. Code Examples #### Using xUnit """csharp using Xunit; using Moq; using System; public interface ICalculator { int Add(int a, int b); } public class CalculatorService { private readonly ICalculator _calculator; public CalculatorService(ICalculator calculator) { _calculator = calculator; } public int PerformCalculation(int a, int b) { return _calculator.Add(a, b); } } public class CalculatorServiceTests { [Fact] public void PerformCalculation_ValidInput_ReturnsSum() { // Arrange var mockCalculator = new Mock<ICalculator>(); mockCalculator.Setup(c => c.Add(2, 3)).Returns(5); var calculatorService = new CalculatorService(mockCalculator.Object); // Act int result = calculatorService.PerformCalculation(2, 3); // Assert Assert.Equal(5, result); mockCalculator.Verify(c => c.Add(2, 3), Times.Once); } } """ **Explanation:** * This example uses "xUnit" for the testing framework and "Moq" for mocking. * "ICalculator" is an interface that "CalculatorService" depends on. * The "PerformCalculation_ValidInput_ReturnsSum" test mocks the "ICalculator" and verifies that "Add" is called with the correct parameters and that the result is as expected. * The "AAA" pattern (Arrange, Act, Assert) is explicitly used for clarity. #### Using NUnit """csharp using NUnit.Framework; using Moq; public interface IOperation { int Execute(int x, int y); } public class OperationService { private readonly IOperation _operation; public OperationService(IOperation operation) { _operation = operation; } public int Calculate(int a, int b) { return _operation.Execute(a, b); } } [TestFixture] public class OperationServiceTests { [Test] public void Calculate_ValidInput_ReturnsResult() { // Arrange var mockOperation = new Mock<IOperation>(); mockOperation.Setup(op => op.Execute(4, 5)).Returns(9); var operationService = new OperationService(mockOperation.Object); // Act int result = operationService.Calculate(4, 5); // Assert Assert.AreEqual(9, result); mockOperation.Verify(op => op.Execute(4, 5), Times.Once); } } """ **Explanation:** * This example uses "NUnit" as the testing framework and "Moq" for mocking. * The "OperationService" class depends on the "IOperation" interface. * In the unit test, a mock of "IOperation" is created. The "Setup" method is used to define the behavior of the mock when "Execute" is called with specific arguments. * The Act section calls the "Calculate" method on the "OperationService" instance. * The Assert section verifies the result and ensures the mocked method was called as expected. ### 2.5. Common Anti-Patterns * **God Class Tests:** Testing multiple functionalities in a single test method. * **Chatty Tests:** Over-mocking or verifying too many interactions. * **Fragile Tests:** Tests that break easily with minor code changes. * **Ignoring Edge Cases:** Failing to test boundary conditions or edge cases. ## 3. Integration Testing Standards Integration testing involves testing the interaction between different components or modules of the application. ### 3.1. Principles of Integration Testing * **Focus on Interactions:** Verify that components work together correctly. * **Use Real Dependencies:** Integrate with real databases or services when appropriate. * **Test Data Isolation:** Ensure that tests do not interfere with each other's data. * **Automated:** Integration tests should be automated for continuous integration. ### 3.2. Do This * **Define Clear Integration Points:** Identify key areas where components interact. * **Use Test Databases:** Set up dedicated test databases to avoid affecting production data. * **Seed Data:** Populate the database with test data before running tests. * **Cleanup Data:** Clean up the test database after running tests. * **Use Dependency Injection:** Facilitate integration by using dependency injection to configure components. ### 3.3. Don't Do This * **Test Everything at Once:** Break down integration tests into smaller, manageable pieces. * **Use Production Data:** Never use production data in integration tests. * **Ignore Error Handling:** Test how components handle errors when interacting with each other. ### 3.4. Code Examples #### Using WebApplicationFactory for Integration Testing """csharp using Microsoft.AspNetCore.Mvc.Testing; using System.Net; using System.Threading.Tasks; using Xunit; public class WebAppFactoryTests : IClassFixture<WebApplicationFactory<Program>> { private readonly WebApplicationFactory<Program> _factory; public WebAppFactoryTests(WebApplicationFactory<Program> factory) { _factory = factory; } [Fact] public async Task Get_EndpointsReturnSuccessAndCorrectContentType() { // Arrange var client = _factory.CreateClient(); // Act var response = await client.GetAsync("/WeatherForecast"); // Assert response.EnsureSuccessStatusCode(); // Status Code 200-299 Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType.ToString()); } [Fact] public async Task Post_ValidInput_ReturnsCreated() { // Arrange var client = _factory.CreateClient(); var content = new StringContent("{ \"Data\": \"Test Data\" }", System.Text.Encoding.UTF8, "application/json"); // Act var response = await client.PostAsync("/api/Data", content); // Assert Assert.Equal(HttpStatusCode.Created, response.StatusCode); } } """ **Explanation:** * This example uses "WebApplicationFactory" to create an in-memory instance of an ASP.NET Core application for integration testing. * The "Get_EndpointsReturnSuccessAndCorrectContentType" test verifies that the "/WeatherForecast" endpoint returns a successful status code and the correct content type. * The "Post_ValidInput_ReturnsCreated" test sends a "POST" request to the "/api/Data" endpoint and verifies that it returns a "Created" status code. ### 3.5. Common Anti-Patterns * **Brittle Integration Tests:** Tests that rely too heavily on implementation details. * **Lack of Data Isolation:** Tests that interfere with each other's data. * **Ignoring Error Scenarios:** Failing to test how components handle errors when integrating with each other. ## 4. End-to-End (E2E) Testing Standards End-to-end testing verifies that the entire application works as expected from start to finish, simulating real user scenarios. ### 4.1. Principles of E2E Testing * **Simulate Real User Scenarios:** Test the most common user workflows. * **Use Real Environments:** Test in environments that closely resemble production. * **Automated:** E2E tests should be automated and run frequently. * **Robust:** Tests should be resilient to minor UI changes. ### 4.2. Do This * **Prioritize Key Scenarios:** Focus on testing the most critical user workflows. * **Use a Testing Framework:** Utilize frameworks like Selenium, Playwright, or Cypress. * **Write Clear Test Cases:** Test cases should describe the steps a user would take. * **Use Page Object Model (POM):** Design tests using the page object model for maintainability. * **Run Tests in Parallel:** Speed up test execution by running tests in parallel. ### 4.3. Don't Do This * **Test Every Possible Scenario:** Focus on the most important ones. * **Make Tests Too Specific:** Keep tests flexible enough to handle minor UI changes. * **Ignore Test Failures:** Investigate and fix failing tests promptly. ### 4.4. Code Examples #### Using Playwright for E2E Testing """csharp using Microsoft.Playwright; using Microsoft.Playwright.NUnit; using NUnit.Framework; using System.Threading.Tasks; public class ExampleTests : PageTest { [Test] public async Task HomepageHasPlaywrightInTitleAndGetStartedLinkLinkingToTheIntroPage() { await Page.GotoAsync("https://playwright.dev/"); // Expect a title "to contain" a substring. await Expect(Page).ToHaveTitleAsync(new System.Text.RegularExpressions.Regex("Playwright")); // create a locator var getStarted = Page.GetByRole(AriaRole.Link, new() { Name = "Get started" }); // Expect an attribute "to be strictly equal" to the expected value. await Expect(getStarted).ToHaveAttributeAsync("href", "/docs/intro"); // Click the get started link. await getStarted.ClickAsync(); // Expects the URL to contain intro. await Expect(Page).ToHaveURLAsync(new System.Text.RegularExpressions.Regex(".*intro")); } } """ **Explanation:** * This example uses Playwright for end-to-end testing. * The "HomepageHasPlaywrightInTitleAndGetStartedLinkLinkingToTheIntroPage" test navigates to the Playwright website, verifies the title, finds a link, confirms an attribute, clicks the link, and finally verifies the URL. #### Using Selenium with C# """csharp using NUnit.Framework; using OpenQA.Selenium; using OpenQA.Selenium.Chrome; using System; [TestFixture] public class SeleniumTests { private IWebDriver driver; [SetUp] public void Setup() { // Initialize ChromeDriver driver = new ChromeDriver(); driver.Manage().Window.Maximize(); } [Test] public void SearchGoogle() { // Navigate to Google driver.Navigate().GoToUrl("https://www.google.com"); // Find the search box, enter text, and submit IWebElement searchBox = driver.FindElement(By.Name("q")); searchBox.SendKeys("Selenium C#"); searchBox.Submit(); // Wait for the search results page to load System.Threading.Thread.Sleep(2000); // Assert that the title contains the search query Assert.IsTrue(driver.Title.Contains("Selenium C#")); } [TearDown] public void TearDown() { // Close the browser driver.Quit(); } } """ **Explanation:** * This example uses Selenium to automate browser interactions. * The "SearchGoogle" test navigates to Google, enters a search query, and asserts that the title contains the search query. * The "Setup" and "TearDown" methods initialize and close the browser, respectively. ### 4.5. Common Anti-Patterns * **Flaky Tests:** Tests that pass or fail intermittently. * **Slow Tests:** Tests that take too long to execute, slowing down the development process. * **Lack of Isolation:** Tests that depend on external factors, such as network connectivity. ## 5. Performance Testing Performance testing is critical to ensure that C# applications perform efficiently under expected loads. It includes load testing, stress testing, and soak testing. ### 5.1. Principles of Performance Testing * **Real-world Scenarios:** Simulate realistic user behavior. * **Measure Key Metrics:** Monitor response times, throughput, and resource utilization. * **Identify Bottlenecks:** Find and address performance issues early in the development cycle. * **Automate Tests:** Incorporate performance tests into the CI/CD pipeline. ### 5.2. Do This * **Define Performance Goals:** Set clear performance targets based on business requirements. * **Use Performance Testing Tools:** Employ tools like JMeter, LoadView, or K6 for load testing. * **Monitor Server Resources:** Track CPU utilization, memory usage, and disk I/O during tests. ### 5.3. Don't Do This * **Ignore Performance Issues:** Address performance issues as seriously as functional bugs. * **Test in Isolation:** Test performance in an environment that closely resembles production. ### 5.4. Code Examples #### Using K6 for Performance Testing (example from K6 documentation) While K6 is typically run from the command line or a script, the configuration can be specified in C# via executing a process . You can then analyze the results in your C# code or integrate them into your testing framework. """csharp using System.Diagnostics; using System.Threading.Tasks; using Xunit; public class K6PerformanceTests { [Fact] public async Task BasicLoadTest() { // Arrange string k6ScriptPath = "path/to/your/k6script.js"; // Replace with actual path. The actual K6 script is still JS string k6Command = $"run {k6ScriptPath}"; // Act var processInfo = new ProcessStartInfo { FileName = "k6", Arguments = k6Command, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; using (var process = new Process()) { process.StartInfo = processInfo; process.Start(); string output = await process.StandardOutput.ReadToEndAsync(); string error = await process.StandardError.ReadToEndAsync(); process.WaitForExit(); Xunit.Assert.Equal(0, process.ExitCode); // Assert that K6 ran successfully Xunit.Assert.True(string.IsNullOrEmpty(error), $"K6 reported errors: {error}"); // TODO: Add assertions based on the K6 output. Parse the output/JSON summary to enforce SLOs System.Console.WriteLine(output); //For debug/inspection of the K6 Results } } } """ **k6script.js (Example K6 script)** """javascript import http from 'k6/http'; import { check, sleep } from 'k6'; export const options = { vus: 10, //Virtual Users duration: '10s', }; export default function () { let res = http.get('https://test.k6.io'); check(res, { 'status is 200': (r) => r.status === 200, }); sleep(1); } """ **Explanation:** * This example shows running a K6 performance test from a C# test. * The C# code creates a process to execute the K6 command-line tool. * The K6 script (k6script.js) defines the load test scenario. * This approach lets you integrate performance testing into your existing C# testing framework. You would parse the output to check assertions and Service Level Objectives/SLOs ### 5.5. Common Anti-Patterns * **Ignoring Performance Testing:** Neglecting to test performance until late in the development cycle. * **Inadequate Load:** Not simulating realistic user loads during testing. * **Lack of Monitoring:** Failing to monitor key performance metrics. ## 6. Security Testing Standards Security testing is essential to ensure that C# applications are protected against vulnerabilities. ### 6.1. Principles of Security Testing * **Identify Vulnerabilities:** Detect potential security flaws early. * **Simulate Attacks:** Mimic real-world attack scenarios to uncover weaknesses. * **Automate Tests:** Include security tests as part of the CI/CD pipeline. * **Regularly Update Tests:** Keep security tests up-to-date with the latest threats. ### 6.2. Do This * **Perform Static Analysis:** Use tools like SonarQube or Fortify to identify code vulnerabilities. * **Conduct Dynamic Analysis:** Employ tools like OWASP ZAP or Burp Suite to test running applications for security flaws. * **Implement Penetration Testing:** Hire ethical hackers to perform manual penetration testing. * **Follow Security Best Practices:** Adhere to secure coding principles and guidelines. * **Use SAST/DAST Tools**: Integrate Static Application Security Testing (SAST) and Dynamic Application Security Testing (DAST) into your CI/CD pipeline. ### 6.3. Don't Do This * **Ignore Security Testing:** Neglecting security testing increases the risk of vulnerabilities. * **Rely Solely on Automated Tools:** Manual testing is also necessary to identify complex vulnerabilities. * **Store Sensitive Data in Code:** Avoid storing passwords or API keys directly in the codebase. ### 6.4. Code Examples #### Example using Static Analysis with Roslyn Analyzers """csharp // Install the Microsoft.CodeAnalysis.FxCopAnalyzers NuGet package // Enable rule CA5398 "Ensure the anti-forgery token is updated when calling methods that require it." using Microsoft.AspNetCore.Mvc; public class HomeController : Controller { [HttpPost] [ValidateAntiForgeryToken] //Ensures protection against Cross-Site Request Forgery (CSRF) attacks. public IActionResult UpdateData(string data) { // Update data logic return View(); } [HttpGet] //Without ValidateAntiForgeryToken, there is still a SAST warning (CA5398) public IActionResult GetData() { // Get data logic return View(); } } """ **Explanation:** * This example shows the use of Roslyn analyzers to perform static analysis. * The "ValidateAntiForgeryToken" attribute helps protect against Cross-Site Request Forgery (CSRF) attacks. The Roslyn analyzer would prompt you if you missed it on a POST. * Static analysis tools can automatically identify potential vulnerabilities in the code. ### 6.5. Common Anti-Patterns * **Hardcoding Credentials:** Storing sensitive information directly in the code. * **Using Weak Encryption:** Employing outdated or insecure encryption algorithms. * **Ignoring Input Validation:** Failing to validate user input, leading to injection attacks. * **Insufficient Access Control:** Allowing unauthorized access to sensitive data or functionality. ## 7. Test-Driven Development (TDD) TDD is a development approach where tests are written before the code. This ensures code is testable and meets the desired requirements. ### 7.1. TDD Cycle * **Red:** Write a failing test. * **Green:** Write the minimum code to pass the test. * **Refactor:** Improve the code while ensuring the test still passes. ### 7.2. Benefits of TDD * **Improved Code Quality:** Code is inherently testable. * **Reduced Defects:** Bugs are identified early in the development process. * **Living Documentation:** Tests serve as documentation of how the code should behave. ### 7.3. Do This * **Write Tests First:** Always write a failing test before writing any code. * **Keep Tests Small:** Each test should focus on a single aspect of the code. * **Refactor Regularly:** Improve the code and tests as needed. ### 7.4. Don't Do This * **Write Code Without Tests:** Avoid writing code without a corresponding test. * **Ignore Failing Tests:** Investigate and fix failing tests promptly. ## 8. Conclusion Adhering to these testing methodologies standards will significantly improve the quality, reliability, and maintainability of C# applications. By incorporating unit, integration, and end-to-end testing, along with performance and security testing, developers can ensure that their applications meet the highest standards of quality and security. Continuously updating and refining these standards based on the latest C# features and best practices is crucial for maintaining a robust and efficient development process.