A powerful command-line tool for comparing public APIs between different versions of .NET assemblies. Designed to help library maintainers and development teams identify breaking changes, track API evolution, and enforce semantic versioning practices.
- Comprehensive API Analysis: Analyzes types, methods, properties, fields, events, and their signatures
- Breaking Change Detection: Automatically identifies and classifies breaking changes vs. non-breaking additions
- Multiple Output Formats: Generate reports in Console, JSON, XML, HTML, and Markdown formats
- Flexible Filtering: Include/exclude specific namespaces, types, or members using patterns
- Configuration-Driven: Use JSON configuration files for complex comparison scenarios
- CI/CD Integration: Perfect for automated builds and release pipelines
- Semantic Versioning Support: Exit codes align with semantic versioning practices
- Compare public APIs between two .NET assembly versions
- Detect breaking changes, additions, removals, and modifications
- Generate reports in multiple formats (Console, JSON, XML, HTML, Markdown)
- Comprehensive analysis of types, methods, properties, fields, and events
- Severity classification for different types of changes
- .NET 8.0 SDK or later
- Compatible with .NET Framework, .NET Core, and .NET 5+ assemblies
# Clone the repository
git clone <repository-url>
cd DotNetApiDiff
# Build the solution
dotnet build
# Run tests
dotnet testThis project uses Taskfile to simplify common development tasks. Make sure you have Task installed:
# macOS
brew install go-task
# Linux
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin
# Windows (with Chocolatey)
choco install go-task# List all available tasks
task
# Build the solution
task build
# Run all tests
task test
# Generate code coverage report
task coverage
# View coverage report in browser
task coverage:view
# Run the application with arguments
task run -- compare source.dll target.dll
# Run CI build sequence
task ciWindows (Chocolatey):
choco install dotnetapidiffmacOS/Linux (Homebrew):
brew install dotnetapidiff# Install latest version to user directory
curl -fsSL https://raw.githubusercontent.com/jbrinkman/dotnet-api-diff/main/install.sh | bash
# Install specific version
curl -fsSL https://raw.githubusercontent.com/jbrinkman/dotnet-api-diff/main/install.sh | bash -s -- v1.2.3
# Install system-wide (requires sudo)
curl -fsSL https://raw.githubusercontent.com/jbrinkman/dotnet-api-diff/main/install.sh | sudo bash -s -- --systemDownload the latest release for your platform from GitHub Releases:
- Linux:
dotnet-api-diff-linux-x64.tar.gzordotnet-api-diff-linux-arm64.tar.gz - macOS:
dotnet-api-diff-osx-x64.tar.gzordotnet-api-diff-osx-arm64.tar.gz - Windows:
dotnet-api-diff-win-x64.zipordotnet-api-diff-win-arm64.zip
Extract the archive and add the binary to your PATH.
# Clone the repository
git clone https://github.com/jbrinkman/dotnet-api-diff.git
cd dotnet-api-diff
# Build the tool
dotnet build
# Or use the Task runner (recommended)
task build# Compare two assembly versions
dotnetapidiff compare MyLibrary.v1.dll MyLibrary.v2.dll
# Generate a JSON report
dotnetapidiff compare source.dll target.dll --output json
# Save report to file
dotnetapidiff compare source.dll target.dll --output markdown > api-changes.md# Simple comparison with console output
dotnetapidiff compare MyLibrary.v1.dll MyLibrary.v2.dll# Filter to specific namespaces
dotnetapidiff compare source.dll target.dll \
--filter "MyLibrary.Core" --filter "MyLibrary.Extensions"
# Filter to specific type patterns
dotnetapidiff compare source.dll target.dll \
--type "MyLibrary.Core.*" --type "MyLibrary.Models.*"
# Exclude internal or test types
dotnetapidiff compare source.dll target.dll \
--exclude "*.Internal*" --exclude "*.Tests*"
# Include internal types (normally excluded)
dotnetapidiff compare source.dll target.dll --include-internals
# Include compiler-generated types
dotnetapidiff compare source.dll target.dll --include-compiler-generated# Generate JSON report
dotnetapidiff compare source.dll target.dll --output json
# Generate Markdown report
dotnetapidiff compare source.dll target.dll --output markdown
# Generate XML report
dotnetapidiff compare source.dll target.dll --output xml
# Generate HTML report
dotnetapidiff compare source.dll target.dll --output html# Use a configuration file for complex scenarios
dotnetapidiff compare source.dll target.dll --config my-config.json
# Enable verbose logging
dotnetapidiff compare source.dll target.dll --verbose
# Disable colored output (useful for CI/CD)
dotnetapidiff compare source.dll target.dll --no-colorUse JSON configuration files to customize comparison behavior for complex scenarios. This is especially useful for:
- Large libraries with complex namespace structures
- APIs that have been refactored or reorganized
- Automated CI/CD pipelines with specific requirements
{
"mappings": {
"namespaceMappings": {
"OldNamespace": ["NewNamespace"],
"Legacy.Api": ["Modern.Api", "Modern.Api.V2"],
"MyLibrary.V1": ["MyLibrary.V2.Core", "MyLibrary.V2.Extensions"]
},
"typeMappings": {
"OldNamespace.OldType": "NewNamespace.NewType",
"Legacy.UserManager": "Modern.Identity.UserService",
"Utils.StringHelper": "Extensions.StringExtensions"
},
"autoMapSameNameTypes": true,
"ignoreCase": true
},
"exclusions": {
"excludedTypes": [
"System.Diagnostics.Debug",
"MyLibrary.Internal.DebugHelper"
],
"excludedTypePatterns": [
"*.Internal.*",
"*.Private.*",
"*.Tests.*",
"*Helper",
"*Utility"
],
"excludedMembers": [
"System.Object.Finalize",
"MyLibrary.BaseClass.InternalMethod"
],
"excludedMemberPatterns": [
"*.Obsolete*",
"*.Debug*",
"get_Internal*",
"set_Internal*"
]
},
"breakingChangeRules": {
"treatTypeRemovalAsBreaking": true,
"treatMemberRemovalAsBreaking": true,
"treatAddedTypeAsBreaking": false,
"treatAddedMemberAsBreaking": false,
"treatSignatureChangeAsBreaking": true
},
"filters": {
"includeNamespaces": [
"MyLibrary.Core",
"MyLibrary.Extensions",
"MyLibrary.Models"
],
"excludeNamespaces": [
"MyLibrary.Internal",
"MyLibrary.Tests",
"System.Diagnostics"
],
"includeTypes": [
"MyLibrary.Core.*",
"MyLibrary.Models.*"
],
"excludeTypes": [
"*.Internal*",
"*.Helper*",
"*Test*"
],
"includeInternals": false,
"includeCompilerGenerated": false
},
"outputFormat": "Console",
"outputPath": null,
"failOnBreakingChanges": true
}The repository includes several sample configuration files for common scenarios:
samples/basic-config.json- Basic configuration for simple librariessamples/enterprise-config.json- Configuration for large enterprise librariessamples/strict-breaking-changes.json- Strict breaking change detectionsamples/lenient-changes.json- Lenient configuration for pre-release versionssamples/namespace-filtering.json- Advanced namespace filtering examples
-
mappings: Define namespace and type name mappings between assemblies
- namespaceMappings: Map source namespaces to target namespaces
- typeMappings: Map specific source types to target types
- autoMapSameNameTypes: Automatically map types with the same name but different namespaces
- ignoreCase: Ignore case when comparing type and namespace names
-
exclusions: Specify types and members to exclude from comparison
- excludedTypes: List of fully qualified type names to exclude
- excludedTypePatterns: Wildcard patterns for excluding types
- excludedMembers: List of fully qualified member names to exclude
- excludedMemberPatterns: Wildcard patterns for excluding members
-
breakingChangeRules: Configure what changes are considered breaking
- treatTypeRemovalAsBreaking: Whether removing a type is a breaking change
- treatMemberRemovalAsBreaking: Whether removing a member is a breaking change
- treatAddedTypeAsBreaking: Whether adding a type is a breaking change
- treatAddedMemberAsBreaking: Whether adding a member is a breaking change
- treatSignatureChangeAsBreaking: Whether changing a member signature is a breaking change
-
filters: Control which types and namespaces are included in the comparison
- includeNamespaces: List of namespaces to include (if empty, all namespaces are included)
- excludeNamespaces: List of namespaces to exclude
- includeTypes: List of type patterns to include
- excludeTypes: List of type patterns to exclude
- includeInternals: Whether to include internal types
- includeCompilerGenerated: Whether to include compiler-generated types
-
outputFormat: Format for the comparison report (Console, Json, Markdown)
-
outputPath: Path to save the report (if null, output to console)
-
failOnBreakingChanges: Whether to return a non-zero exit code if breaking changes are found
Create .github/workflows/api-compatibility.yml:
name: API Compatibility Check
on:
pull_request:
branches: [ main ]
jobs:
api-compatibility:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Need full history to compare with main
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Build current version
run: dotnet build --configuration Release
- name: Checkout main branch
run: |
git checkout main
dotnet build --configuration Release --output ./baseline
git checkout -
- name: Build PR version
run: dotnet build --configuration Release --output ./current
- name: Run API Diff
run: |
dotnetapidiff compare \
./baseline/MyLibrary.dll \
./current/MyLibrary.dll \
--config .github/api-diff-config.json \
--output markdown \
--no-color > api-changes.md
- name: Comment PR with API Changes
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const apiChanges = fs.readFileSync('api-changes.md', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## API Changes\n\n${apiChanges}`
});Create azure-pipelines-api-check.yml:
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
buildConfiguration: 'Release'
steps:
- task: DotNetCoreCLI@2
displayName: 'Build Baseline'
inputs:
command: 'build'
configuration: $(buildConfiguration)
outputDir: '$(Build.ArtifactStagingDirectory)/baseline'
- task: DotNetCoreCLI@2
displayName: 'Build Current'
inputs:
command: 'build'
configuration: $(buildConfiguration)
outputDir: '$(Build.ArtifactStagingDirectory)/current'
- task: DotNetCoreCLI@2
displayName: 'Run API Diff'
inputs:
command: 'custom'
custom: 'dotnetapidiff'
arguments: >
compare
$(Build.ArtifactStagingDirectory)/baseline/MyLibrary.dll
$(Build.ArtifactStagingDirectory)/current/MyLibrary.dll
--config api-diff-config.json
--output json
--no-color
continueOnError: true
- task: PublishTestResults@2
displayName: 'Publish API Diff Results'
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: 'api-diff-results.xml'The tool uses semantic exit codes to integrate with CI/CD systems:
- 0: Success - Comparison completed successfully with no breaking changes detected
- 1: Breaking Changes - Comparison completed successfully but breaking changes were detected
- 2: Comparison Error - An error occurred during the comparison process
- 3: Assembly Load Error - Failed to load one or more assemblies for comparison
- 4: Configuration Error - Configuration error or invalid settings detected
- 5: Invalid Arguments - Invalid command line arguments provided
- 6: File Not Found - One or more required files could not be found
- 99: Unexpected Error - An unexpected error occurred during execution
#!/bin/bash
# Run API diff and capture exit code
dotnetapidiff compare old.dll new.dll --config config.json
EXIT_CODE=$?
case $EXIT_CODE in
0)
echo "β
Comparison completed successfully with no breaking changes"
;;
1)
echo "β οΈ Breaking changes detected - review before release"
exit 1
;;
2)
echo "β Comparison error occurred"
exit 1
;;
3)
echo "π₯ Assembly loading failed"
exit 1
;;
4)
echo "βοΈ Configuration error"
exit 1
;;
5)
echo "π Invalid command line arguments"
exit 1
;;
6)
echo "π Required files not found"
exit 1
;;
99)
echo "π₯ Unexpected error occurred"
exit 1
;;
*)
echo "β Unknown exit code: $EXIT_CODE"
exit 1
;;
esac# Download previous version from NuGet
nuget install MyLibrary -Version 1.0.0 -OutputDirectory packages
# Compare with current build
dotnetapidiff compare \
packages/MyLibrary.1.0.0/lib/net8.0/MyLibrary.dll \
src/MyLibrary/bin/Release/net8.0/MyLibrary.dll \
--output json > api-changes.json
# Parse results for automated decision making
if [ $? -eq 1 ]; then
echo "Breaking changes detected - increment major version"
elif [ $? -eq 0 ]; then
echo "No breaking changes detected - increment minor or patch version"
else
echo "Error occurred during comparison - check logs"
exit 1
fi# Compare .NET Framework vs .NET Core implementations
dotnetapidiff compare \
MyLibrary.net48.dll \
MyLibrary.net8.0.dll \
--config framework-migration-config.json \
--output html > framework-differences.html# Lenient checking for pre-release versions
dotnetapidiff compare \
MyLibrary.1.0.0-stable.dll \
MyLibrary.1.1.0-beta.dll \
--config samples/lenient-changes.json \
--output markdown<sourceAssembly>: Path to the source/baseline assembly (required)<targetAssembly>: Path to the target/current assembly (required)--config, -c: Path to configuration file--output, -o: Output format (console, json, xml, html, markdown)--filter, -f: Filter to specific namespaces (can be specified multiple times)--type, -t: Filter to specific type patterns (can be specified multiple times)--exclude, -e: Exclude types matching pattern (can be specified multiple times)--include-internals: Include internal types in the comparison--include-compiler-generated: Include compiler-generated types in the comparison--no-color: Disable colored output (useful for CI/CD)--verbose, -v: Enable verbose output--help, -h: Show help information
src/
βββ DotNetApiDiff/
β βββ Interfaces/ # Core service interfaces
β βββ Models/ # Data models and enums
β βββ Services/ # Implementation classes (to be added)
β βββ Program.cs # Main entry point
tests/
βββ DotNetApiDiff.Tests/ # Unit tests
Assembly Loading Errors
# Ensure assemblies are built for compatible target frameworks
dotnetapidiff compare source.dll target.dll --verboseMissing Dependencies
# Place all dependencies in the same directory as the assemblies
# Or use the --assembly-path option to specify additional search pathsLarge Memory Usage
# For very large assemblies, consider filtering to specific namespaces
dotnetapidiff compare source.dll target.dll \
--filter "MyLibrary.Core" --exclude "*.Internal*"Performance Issues
# Use configuration files to exclude unnecessary types
dotnetapidiff compare source.dll target.dll \
--config samples/enterprise-config.jsonEnable detailed logging for troubleshooting:
# Set environment variable for detailed logging
export DOTNET_API_DIFF_LOG_LEVEL=Debug
# Run with verbose output
dotnetapidiff compare source.dll target.dll --verboseExtend the tool with custom report generators:
public class CustomReportGenerator : IReportGenerator
{
public string GenerateReport(ComparisonResult result, ReportFormat format)
{
// Custom report logic
return customReport;
}
}Use the tool as a library in your own applications:
var services = new ServiceCollection();
// Configure services...
var serviceProvider = services.BuildServiceProvider();
var comparer = serviceProvider.GetRequiredService<IApiComparer>();
var result = await comparer.CompareAssembliesAsync(sourceAssembly, targetAssembly);
if (result.HasBreakingChanges)
{
Console.WriteLine($"Found {result.BreakingChangesCount} breaking changes");
}MSBuild Integration
<Target Name="CheckApiCompatibility" BeforeTargets="Pack">
<Exec Command="dotnetapidiff compare $(PreviousVersionDll) $(OutputPath)$(AssemblyName).dll --config api-config.json"
ContinueOnError="false" />
</Target>Cake Build Integration
Task("CheckApiCompatibility")
.Does(() =>
{
var exitCode = StartProcess("dotnetapidiff", new ProcessSettings
{
Arguments = "compare baseline.dll current.dll --config config.json"
});
if (exitCode == 1)
{
throw new Exception("Breaking changes detected!");
}
else if (exitCode != 0)
{
throw new Exception($"API comparison failed with exit code {exitCode}");
}
});We welcome contributions! Here's how to get started:
# Clone the repository
git clone https://github.com/jbrinkman/dotnet-api-diff.git
cd dotnet-api-diff
# Install dependencies
dotnet restore
# Run tests
task test
# Run with coverage
task coverage- Fork the repository and create a feature branch
- Write tests for new functionality
- Follow coding standards (enforced by StyleCop)
- Update documentation for user-facing changes
- Ensure all tests pass including code coverage requirements
- Submit a pull request with a clear description
This project is licensed under the MIT License - see the LICENSE file for details.
- Built with Spectre.Console for beautiful console output
- Uses System.Reflection.Metadata for efficient assembly analysis
- Inspired by the need for better API compatibility tooling in the .NET ecosystem