C# Language-Specific Rules
Based on Microsoft C# Coding Conventions, StyleCop Analyzers, and .NET Best Practices
Formatting Standards (C# Specific)
Indentation: 4 spaces (no tabs)
This follows the Microsoft C# Coding Conventions and StyleCop default rules, which are the industry standard for .NET development.
Note: Each language follows its community's indentation standards:
- C++: 2 spaces (Google C++ Style Guide)
- C#: 4 spaces (Microsoft standard)
- Java: 4 spaces (Google Java Style Guide)
- JavaScript/TypeScript: 2 spaces (ecosystem standard)
- Kotlin: 4 spaces (JetBrains standard)
- Python: 4 spaces (PEP 8 mandatory)
Example:
namespace Acme.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
/// <summary>
/// Service for managing user data.
/// </summary>
public class UserService
{
private readonly IUserRepository _repository;
private readonly IEmailService _emailService;
public UserService(IUserRepository repository, IEmailService emailService)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
_emailService = emailService ?? throw new ArgumentNullException(nameof(emailService));
}
/// <summary>
/// Retrieves a user by ID.
/// </summary>
/// <param name="id">The user ID.</param>
/// <returns>The user if found, null otherwise.</returns>
public async Task<User?> GetUserByIdAsync(long id)
{
// 4-space indentation throughout
if (id <= 0)
{
throw new ArgumentException("ID must be positive", nameof(id));
}
var user = await _repository.FindByIdAsync(id);
if (user != null)
{
user.LastAccessed = DateTime.UtcNow;
await _repository.UpdateAsync(user);
}
return user;
}
/// <summary>
/// Creates a new user.
/// </summary>
/// <param name="userData">The user data.</param>
/// <returns>The created user.</returns>
public async Task<User> CreateUserAsync(UserData userData)
{
ArgumentNullException.ThrowIfNull(userData);
var user = new User
{
Name = userData.Name,
Email = userData.Email,
CreatedAt = DateTime.UtcNow
};
await _repository.SaveAsync(user);
await _emailService.SendWelcomeEmailAsync(user);
return user;
}
}
```text
---
## Overview
This file contains C#-specific best practices including:
- **Microsoft C# Coding Conventions** - Official .NET standards
- **StyleCop Analyzers** - Industry-standard static analysis
- **.NET Best Practices** - Modern .NET patterns
- **C# 12 Features** - Latest language features
- **Async/Await Patterns** - Asynchronous programming best practices
---
## Quick Standards Summary
### Formatting
- **Indentation:** 4 spaces (no tabs)
- **Brace Style:** Allman style (opening brace on new line)
- **Line Length:** 120 characters (soft limit)
- **One Statement Per Line:** No multiple statements on one line
- **Block Indentation:** +4 spaces
- **File-scoped Namespaces:** Use file-scoped namespace declarations (C# 10+)
### Naming Conventions
| Type | Convention | Example |
|------|------------|---------|
| Namespace | PascalCase | `Acme.Services` |
| Class | PascalCase | `UserService` |
| Interface | IPascalCase (I prefix) | `IUserRepository` |
| Method | PascalCase | `GetUserById` |
| Property | PascalCase | `FirstName` |
| Event | PascalCase | `DataReceived` |
| Constant | PascalCase | `MaxRetries` |
| Private Field | _camelCase (underscore prefix) | `_repository` |
| Parameter | camelCase | `userId` |
| Local Variable | camelCase | `user` |
| Type Parameter | TPascalCase (T prefix) | `TEntity` |
### Modifier Order (StyleCop SA1206)
```csharp
public class Example
{
// Correct modifier order:
// 1. Access modifiers (public, protected, internal, private)
// 2. static
// 3. abstract/virtual/override
// 4. readonly/const
// 5. extern
// 6. new
// 7. unsafe
// 8. volatile
// 9. async
public static readonly string Constant = "value";
private readonly IDependency _dependency;
protected virtual async Task<int> ProcessAsync()
{
return await Task.FromResult(0);
}
}
```text
### Class Structure (StyleCop Ordering)
```csharp
public class Example
{
// 1. Constants
private const int MaxRetries = 3;
// 2. Static fields
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
// 3. Instance fields
private readonly IDependency _dependency;
private string _cachedValue;
// 4. Constructors
public Example(IDependency dependency)
{
_dependency = dependency;
}
// 5. Finalizers (destructors)
~Example()
{
// Cleanup
}
// 6. Delegates
public delegate void DataHandler(object sender, EventArgs e);
// 7. Events
public event DataHandler DataReceived;
// 8. Properties
public string Name { get; set; }
public int Age { get; private set; }
// 9. Indexers
public string this[int index] => _items[index];
// 10. Methods (public first, then protected, then private)
public void PublicMethod()
{
// ...
}
protected void ProtectedMethod()
{
// ...
}
private void PrivateMethod()
{
// ...
}
// 11. Nested types
public class NestedClass
{
// ...
}
}
```text
### Modern C# Features (C# 10+)
```csharp
// File-scoped namespace (C# 10+)
namespace Acme.Services;
// Global usings (defined in separate GlobalUsings.cs)
// global using System;
// global using System.Collections.Generic;
// Records (C# 9+)
public record User(long Id, string Name, string Email)
{
// Init-only properties
public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
// With expressions for immutable updates
public User WithName(string newName) => this with { Name = newName };
}
// Record structs (C# 10+)
public readonly record struct Point(double X, double Y);
// Pattern matching
public string ClassifyUser(User user) => user switch
{
{ Age: < 18 } => "Minor",
{ Age: >= 18 and < 65 } => "Adult",
{ Age: >= 65 } => "Senior",
_ => "Unknown"
};
// Target-typed new (C# 9+)
List<string> names = new();
User user = new() { Name = "John", Email = "john@example.com" };
// Null-coalescing assignment (C# 8+)
_cache ??= new Dictionary<string, object>();
// Nullable reference types (C# 8+)
#nullable enable
public string? FindUser(int id) // May return null
{
return _users.ContainsKey(id) ? _users[id] : null;
}
// Required properties (C# 11+)
public class Config
{
public required string ApiUrl { get; init; }
public required string ApiKey { get; init; }
}
// Raw string literals (C# 11+)
string json = """
{
"name": "John",
"email": "john@example.com"
}
""";
// List patterns (C# 11+)
public bool IsValidSequence(int[] numbers) => numbers switch
{
[1, 2, 3] => true,
[var first, .., var last] when first == last => true,
_ => false
};
```text
### Async/Await Best Practices
```csharp
// Always use Async suffix for async methods
public async Task<User> GetUserAsync(int id)
{
return await _repository.FindByIdAsync(id);
}
// ConfigureAwait(false) for library code
public async Task<Data> FetchDataAsync()
{
return await _httpClient.GetAsync(url).ConfigureAwait(false);
}
// ValueTask for hot paths
public ValueTask<int> GetCachedValueAsync(string key)
{
if (_cache.TryGetValue(key, out var value))
{
return new ValueTask<int>(value);
}
return new ValueTask<int>(FetchFromDatabaseAsync(key));
}
// Cancellation tokens
public async Task<User> GetUserAsync(int id, CancellationToken cancellationToken)
{
return await _repository.FindByIdAsync(id, cancellationToken);
}
// Task.WhenAll for parallel operations
public async Task<IEnumerable<User>> GetUsersAsync(IEnumerable<int> ids)
{
var tasks = ids.Select(id => GetUserAsync(id));
return await Task.WhenAll(tasks);
}
```text
### LINQ Best Practices
```csharp
// Method syntax for complex queries
var adults = users
.Where(u => u.Age >= 18)
.OrderBy(u => u.Name)
.Select(u => new UserDto
{
Name = u.Name,
Email = u.Email
})
.ToList();
// Query syntax for joins
var userOrders = from user in users
join order in orders on user.Id equals order.UserId
where order.Total > 100
select new { user.Name, order.Total };
// Use Any() instead of Count() > 0
if (users.Any(u => u.IsActive))
{
// More efficient than users.Count(u => u.IsActive) > 0
}
// Use SingleOrDefault for single results
var user = users.SingleOrDefault(u => u.Id == targetId);
```text
---
## StyleCop Analyzer Rules
**MANDATORY:** All C# code must pass StyleCop Analyzers checks.
### Installation
Add to your project file:
```xml
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
```text
### StyleCop Configuration (stylecop.json)
```json
{
"$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
"settings": {
"documentationRules": {
"companyName": "Cortexa LLC",
"copyrightText": "Copyright (c) {companyName}. All rights reserved.",
"xmlHeader": true,
"fileNamingConvention": "stylecop"
},
"orderingRules": {
"usingDirectivesPlacement": "outsideNamespace",
"systemUsingDirectivesFirst": true
},
"namingRules": {
"allowCommonHungarianPrefixes": false,
"allowedHungarianPrefixes": []
}
}
}
```text
### Critical StyleCop Rules
#### Documentation Rules (SA1600-SA1648)
- **SA1600:** Elements must be documented
- **SA1633:** File must have header
- **SA1642:** Constructor summary documentation must begin with standard text
```csharp
/// <summary>
/// Represents a user in the system.
/// </summary>
public class User
{
/// <summary>
/// Initializes a new instance of the <see cref="User"/> class.
/// </summary>
/// <param name="id">The user ID.</param>
/// <param name="name">The user name.</param>
public User(int id, string name)
{
Id = id;
Name = name;
}
/// <summary>
/// Gets or sets the user ID.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Gets or sets the user name.
/// </summary>
public string Name { get; set; }
}
```text
#### Spacing Rules (SA1000-SA1028)
- **SA1000:** Keywords must be spaced correctly
- **SA1001:** Commas must be spaced correctly
- **SA1008:** Opening parenthesis must be spaced correctly
- **SA1009:** Closing parenthesis must be spaced correctly
```csharp
// Correct spacing
if (condition)
{
DoSomething(param1, param2);
}
// Wrong spacing
if(condition)
{
DoSomething(param1,param2);
}
```text
#### Readability Rules (SA1100-SA1142)
- **SA1101:** Prefix local calls with this (disabled by default)
- **SA1116:** Split parameters should start on line after declaration
- **SA1117:** Parameters must be on same line or separate lines
- **SA1124:** Do not use regions
- **SA1133:** Do not combine attributes
```csharp
// Correct attribute placement
[Serializable]
[DataContract]
public class User
{
[DataMember]
public string Name { get; set; }
}
// Wrong attribute placement
[Serializable, DataContract] // SA1133 violation
public class User
{
}
```text
#### Ordering Rules (SA1200-SA1214)
- **SA1200:** Using directives must be placed correctly
- **SA1201:** Elements must appear in the correct order
- **SA1202:** Elements must be ordered by access
- **SA1204:** Static elements must appear before instance elements
```csharp
// Correct using placement (outside namespace)
using System;
using System.Collections.Generic;
namespace Acme.Services
{
public class Example
{
// Constants first
private const int MaxSize = 100;
// Static fields before instance fields
private static readonly Logger _logger = new();
private readonly IService _service;
// Public members before private
public void PublicMethod() { }
private void PrivateMethod() { }
}
}
```text
#### Naming Rules (SA1300-SA1313)
- **SA1300:** Element must begin with upper-case letter
- **SA1302:** Interface names must begin with I
- **SA1303:** Const field names must begin with upper-case letter
- **SA1306:** Field names must begin with lower-case letter
- **SA1307:** Accessible fields must begin with upper-case letter
- **SA1309:** Field names must not begin with underscore (disabled - we use underscore for private fields)
```csharp
// Correct naming
public interface IUserService { }
public class UserService { }
private readonly IRepository _repository;
public const int MaxRetries = 3;
```text
#### Maintainability Rules (SA1400-SA1413)
- **SA1400:** Access modifier must be declared
- **SA1401:** Fields must be private
- **SA1402:** File may only contain a single type
- **SA1404:** Code analysis suppression must have justification
```csharp
// Each type in separate file
// File: UserService.cs
public class UserService // SA1400: explicit 'public'
{
private readonly IRepository _repository; // SA1401: private field
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600",
Justification = "Internal utility method")] // SA1404: justification provided
internal void UtilityMethod() { }
}
```text
---
## .NET Best Practices
### Dependency Injection
```csharp
// Use constructor injection
public class UserService
{
private readonly IUserRepository _repository;
private readonly ILogger<UserService> _logger;
public UserService(IUserRepository repository, ILogger<UserService> logger)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
}
// Register in Program.cs
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<UserService>();
```text
### Exception Handling
```csharp
// Be specific with exceptions
public User GetUser(int id)
{
if (id <= 0)
{
throw new ArgumentException("ID must be positive", nameof(id));
}
var user = _repository.Find(id);
if (user == null)
{
throw new UserNotFoundException($"User with ID {id} not found");
}
return user;
}
// Use guard clauses
public void ProcessOrder(Order order)
{
ArgumentNullException.ThrowIfNull(order);
if (order.Items.Count == 0)
{
throw new InvalidOperationException("Order must contain items");
}
// Process order
}
// Don't catch generic exceptions
try
{
ProcessData();
}
catch (IOException ex) // Specific exception
{
_logger.LogError(ex, "Failed to read data");
throw;
}
catch (Exception) // Avoid this
{
// Too generic
}
```text
### Resource Management
```csharp
// Use 'using' declarations (C# 8+)
public async Task<string> ReadFileAsync(string path)
{
using var stream = File.OpenRead(path);
using var reader = new StreamReader(stream);
return await reader.ReadToEndAsync();
}
// IDisposable implementation
public class DatabaseConnection : IDisposable
{
private bool _disposed;
private SqlConnection _connection;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// Dispose managed resources
_connection?.Dispose();
}
// Dispose unmanaged resources
_disposed = true;
}
}
~DatabaseConnection()
{
Dispose(false);
}
}
```text
### Collections
```csharp
// Use collection expressions (C# 12+)
List<int> numbers = [1, 2, 3, 4, 5];
int[] array = [1, 2, 3];
// Use IReadOnlyCollection/IReadOnlyList for immutable interfaces
public IReadOnlyList<User> GetUsers()
{
return _users.AsReadOnly();
}
// Use immutable collections for thread-safety
private readonly ImmutableList<string> _allowedRoles =
ImmutableList.Create("Admin", "User", "Guest");
```text
### Performance
```csharp
// Use Span<T> for performance-critical code
public int Sum(ReadOnlySpan<int> numbers)
{
int total = 0;
foreach (var num in numbers)
{
total += num;
}
return total;
}
// Use StringBuilder for string concatenation
public string BuildReport(IEnumerable<User> users)
{
var sb = new StringBuilder();
foreach (var user in users)
{
sb.AppendLine($"{user.Name}: {user.Email}");
}
return sb.ToString();
}
// Use ArrayPool for temporary arrays
var pool = ArrayPool<byte>.Shared;
byte[] buffer = pool.Rent(1024);
try
{
// Use buffer
}
finally
{
pool.Return(buffer);
}
```text
---
## EditorConfig for C#
Add to `.editorconfig`:
```ini
# C# files
[*.cs]
# Indentation
indent_style = space
indent_size = 4
tab_width = 4
# New line preferences
end_of_line = crlf
insert_final_newline = true
charset = utf-8-bom
# Organize usings
dotnet_sort_system_directives_first = true
dotnet_separate_import_directive_groups = false
# Language conventions
dotnet_style_qualification_for_field = false:warning
dotnet_style_qualification_for_property = false:warning
dotnet_style_qualification_for_method = false:warning
dotnet_style_qualification_for_event = false:warning
# Use language keywords instead of framework type names
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
dotnet_style_predefined_type_for_member_access = true:warning
# Modifier preferences
dotnet_style_require_accessibility_modifiers = always:warning
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning
# Expression preferences
dotnet_style_prefer_auto_properties = true:warning
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_return = false:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
# Pattern matching
csharp_style_pattern_matching_over_is_with_cast_check = true:warning
csharp_style_pattern_matching_over_as_with_null_check = true:warning
# Null checking
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:warning
# Expression-bodied members
csharp_style_expression_bodied_methods = when_on_single_line:suggestion
csharp_style_expression_bodied_constructors = false:suggestion
csharp_style_expression_bodied_operators = when_on_single_line:suggestion
csharp_style_expression_bodied_properties = when_on_single_line:suggestion
csharp_style_expression_bodied_indexers = when_on_single_line:suggestion
csharp_style_expression_bodied_accessors = when_on_single_line:suggestion
# Var preferences
csharp_style_var_for_built_in_types = true:warning
csharp_style_var_when_type_is_apparent = true:warning
csharp_style_var_elsewhere = true:suggestion
# Code style
csharp_prefer_braces = true:warning
csharp_prefer_simple_using_statement = true:suggestion
csharp_style_namespace_declarations = file_scoped:warning
csharp_style_prefer_method_group_conversion = true:suggestion
csharp_style_prefer_top_level_statements = true:suggestion
# Formatting
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true
# Spacing
csharp_space_after_cast = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_after_colon_in_inheritance_clause = true
# Wrapping
csharp_preserve_single_line_statements = false
csharp_preserve_single_line_blocks = true
```text
---
## TODO: Full C# Guidelines
This file will be expanded to include:
- [ ] ASP.NET Core best practices
- [ ] Entity Framework Core patterns
- [ ] Blazor component patterns
- [ ] Testing with xUnit and NUnit
- [ ] Minimal APIs patterns
- [ ] gRPC service patterns
- [ ] Logging and monitoring
- [ ] Security best practices
- [ ] Performance optimization
---
**For now, always use 4-space indentation per Microsoft conventions. All code must pass StyleCop Analyzers checks. Full guidelines coming soon.**