| name | C# |
| description | Execute these commands after EVERY implementation (see AGENT_AUTOMATION module for full workflow). |
| version | 1.0.0 |
| category | languages |
| author | Rulebook |
| tags | ["languages","language"] |
| dependencies | [] |
| conflicts | [] |
C# Project Rules
Agent Automation Commands
CRITICAL: Execute these commands after EVERY implementation (see AGENT_AUTOMATION module for full workflow).
dotnet format --verify-no-changes
dotnet build
dotnet test
dotnet test --collect:"XPlat Code Coverage"
dotnet list package --vulnerable
dotnet list package --outdated
C# Configuration
CRITICAL: Use .NET 8+ with C# 12+.
- Version: .NET 8.0+
- C# Version: 12+
- Target: net8.0
- Nullable: Enabled
- LangVersion: latest
Project File Requirements
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AnalysisMode>All</AnalysisMode>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<PackageId>Your.Package.Name</PackageId>
<Version>1.0.0</Version>
<Authors>Your Name</Authors>
<Company>Your Company</Company>
<Description>A short description of your package</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/your-org/your-project</PackageProjectUrl>
<RepositoryUrl>https://github.com/your-org/your-project</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>your;tags</PackageTags>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
Code Quality Standards
Mandatory Quality Checks
CRITICAL: After implementing ANY feature, you MUST run these commands in order.
IMPORTANT: These commands MUST match your GitHub Actions workflows to prevent CI/CD failures!
dotnet format --verify-no-changes
dotnet build --no-incremental --warnaserror
dotnet test --no-build
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
If ANY of these fail, you MUST fix the issues before committing.
Why This Matters:
- CI/CD failures happen when local commands differ from workflows
- Example: Using
dotnet format locally but dotnet format --verify-no-changes in CI = failure
- Example: Missing
--warnaserror flag = warnings pass locally but fail in CI
Code Style
Use .editorconfig for consistent code style:
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
[*.{cs,csx,vb,vbx}]
indent_size = 4
[*.cs]
dotnet_sort_system_directives_first = true
dotnet_separate_import_directive_groups = false
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
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
dotnet_style_predefined_type_for_member_access = true:warning
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
csharp_style_var_for_built_in_types = true:warning
csharp_style_var_when_type_is_apparent = true:warning
csharp_style_var_elsewhere = true:warning
csharp_style_pattern_matching_over_is_with_cast_check = true:warning
csharp_style_pattern_matching_over_as_with_null_check = true:warning
csharp_style_throw_expression = true:warning
csharp_style_conditional_delegate_call = true:warning
csharp_prefer_braces = true:warning
csharp_prefer_simple_using_statement = true:warning
dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = warning
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
Testing
- Framework: xUnit (recommended) or NUnit
- Location: Separate test project
- Coverage: Coverlet
- Coverage Threshold: 95%+
Example test structure:
using Xunit;
namespace YourProject.Tests;
public class MyClassTests
{
[Fact]
public void Process_ValidInput_ReturnsExpectedResult()
{
var sut = new MyClass();
var input = "test";
var result = sut.Process(input);
Assert.Equal("TEST", result);
}
[Theory]
[InlineData("")]
[InlineData(null)]
public void Process_InvalidInput_ThrowsArgumentException(string input)
{
var sut = new MyClass();
Assert.Throws<ArgumentException>(() => sut.Process(input));
}
}
Documentation
- Use XML documentation comments
- Document all public APIs
- Include
<summary>, <param>, <returns>, <exception>
Example:
namespace YourProject;
public class MyClass
{
public string Process(string input)
{
if (string.IsNullOrEmpty(input))
{
throw new ArgumentException("Input cannot be null or empty.", nameof(input));
}
return input.ToUpperInvariant();
}
}
Project Structure
project/
├── src/
│ └── YourProject/
│ ├── YourProject.csproj
│ ├── Class1.cs
│ └── ...
├── tests/
│ └── YourProject.Tests/
│ ├── YourProject.Tests.csproj
│ ├── Class1Tests.cs
│ └── ...
├── docs/ # Project documentation
├── .editorconfig # Code style configuration
├── Directory.Build.props # Shared MSBuild properties
├── Directory.Packages.props # Central package management
├── YourProject.sln # Solution file
├── README.md # Project overview (allowed in root)
├── CHANGELOG.md # Version history (allowed in root)
└── LICENSE # Project license (allowed in root)
Nullable Reference Types
- Enable nullable reference types
- Use
? for nullable types
- Use null-forgiving operator
! sparingly
Example:
public class UserService
{
private readonly ILogger<UserService> _logger;
public UserService(ILogger<UserService> logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public User? FindUser(string? username)
{
if (string.IsNullOrEmpty(username))
{
return null;
}
return new User { Username = username };
}
public User GetUser(string username)
{
var user = FindUser(username);
return user ?? throw new InvalidOperationException("User not found");
}
}
Async/Await Best Practices
- Use
async/await for I/O operations
- Don't block on async code
- Use
ConfigureAwait(false) in libraries
- Return
Task or ValueTask
Example:
public class DataService
{
private readonly HttpClient _httpClient;
public async Task<string> FetchDataAsync(string url, CancellationToken cancellationToken = default)
{
var response = await _httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
}
}
CI/CD Requirements
Must include GitHub Actions workflows for:
-
Testing (dotnet-test.yml):
- Test on ubuntu-latest, windows-latest, macos-latest
- Test on .NET 8.0
- Upload coverage reports
-
Linting (dotnet-lint.yml):
- Format check:
dotnet format --verify-no-changes
- Build:
dotnet build --no-incremental
- Analyzers enabled
Package Publication
Publishing to NuGet
Prerequisites:
- Create account at https://www.nuget.org
- Generate API key from account settings
- Add
NUGET_API_KEY to GitHub repository secrets
Publishing Workflow:
-
Update version in .csproj
-
Update CHANGELOG.md
-
Run quality checks:
dotnet format --verify-no-changes
dotnet build --configuration Release
dotnet test --configuration Release
-
Pack: dotnet pack --configuration Release
-
Create git tag: git tag v1.0.0 && git push --tags
-
GitHub Actions automatically publishes to NuGet
-
Or manual publish: dotnet nuget push bin/Release/*.nupkg --api-key $NUGET_API_KEY --source https://api.nuget.org/v3/index.json
Publishing Checklist:
- ✅ All tests passing
- ✅ Code formatted (
dotnet format)
- ✅ No build warnings
- ✅ Version updated in .csproj
- ✅ CHANGELOG.md updated
- ✅ README.md up to date
- ✅ LICENSE file present
- ✅ XML documentation generated
- ✅ Package metadata complete
- ✅ Verify with
dotnet pack
Semantic Versioning:
Use <Version> in .csproj with SemVer:
- MAJOR: Breaking API changes
- MINOR: New features (backwards compatible)
- PATCH: Bug fixes (backwards compatible)