Code Fixes
UnionGenerator provides automatic code fixes that help you quickly resolve analyzer warnings. These refactorings appear as light bulb suggestions in your IDE.
π― Overviewβ
Code fix providers automatically generate solutions for analyzer diagnostics. Press Ctrl+. (Windows/Linux) or Cmd+. (Mac) when your cursor is on a diagnostic to see available fixes.
π§ Available Code Fixesβ
1. Add Missing Match Casesβ
Triggers on: UG1001 (Incomplete pattern matching)
What it does: Automatically adds missing handler parameters to .Match() calls.
Beforeβ
[GenerateUnion]
public partial record Result<T>
{
public static partial Result<T> Success(T value);
public static partial Result<T> Error(string message);
}
// β οΈ UG1001: Missing 'error' parameter
var output = result.Match(
success: value => $"Value: {value}"
);
After (Code Fix Applied)β
// β
All cases handled
var output = result.Match(
success: value => $"Value: {value}",
error: message => throw new NotImplementedException()
);
Default behavior: Inserts throw new NotImplementedException() for missing handlers, prompting you to implement the logic.
2. Add Missing Switch Armsβ
Triggers on: UG002 (Missing union case in switch)
What it does: Adds missing pattern arms to switch expressions.
Beforeβ
// β οΈ UG002: Missing 'Failed' case
var message = status switch
{
{ IsPending: true } => "Processing",
{ IsCompleted: true } => "Done"
};
After (Code Fix Applied)β
// β
All cases covered
var message = status switch
{
{ IsPending: true } => "Processing",
{ IsCompleted: true } => "Done",
{ IsFailed: true } => throw new NotImplementedException()
};
3. Add Discard Armβ
Triggers on: UG002 (Missing union case in switch)
What it does: Adds a catch-all discard pattern (_) instead of explicit cases.
Beforeβ
// β οΈ UG002: Missing cases
var isSuccessful = result switch
{
{ IsSuccess: true } => true
};
After (Code Fix Applied - Option 2)β
// β
Discard handles remaining cases
var isSuccessful = result switch
{
{ IsSuccess: true } => true,
_ => false
};
4. Add Debugger Visualizationβ
Triggers on: UG3002 (Missing debugger attributes)
What it does: Adds [DebuggerDisplay] and [DebuggerTypeProxy] attributes to union types.
Beforeβ
// β οΈ UG3002: Missing debugger visualization
[GenerateUnion]
public partial record ApiResult
{
public static partial ApiResult Success(Data data);
public static partial ApiResult Error(string message);
}
After (Code Fix Applied)β
// β
Debug visualization added
[DebuggerDisplay("{GetDebuggerDisplay(),nq}")]
[DebuggerTypeProxy(typeof(ApiResultDebugView))]
[GenerateUnion]
public partial record ApiResult
{
public static partial ApiResult Success(Data data);
public static partial ApiResult Error(string message);
private string GetDebuggerDisplay() =>
Match(
success: d => $"Success: {d}",
error: e => $"Error: {e}"
);
}
internal class ApiResultDebugView
{
private readonly ApiResult _result;
public ApiResultDebugView(ApiResult result)
{
_result = result;
}
public string CurrentCase => _result.Match(
success: _ => nameof(Success),
error: _ => nameof(Error)
);
public object? Value => _result.Match<object?>(
success: d => d,
error: e => e
);
}
5. Use Generated OneOf Adapterβ
Triggers on: UG3001 (Repetitive OneOf if/else chain)
What it does: Replaces if/else chains checking IsT0, IsT1 with generated adapter methods.
Beforeβ
// β οΈ UG3001: Can be simplified
OneOf<string, int> oneOfValue = GetValue();
if (oneOfValue.IsT0)
{
var str = oneOfValue.AsT0;
Console.WriteLine($"String: {str}");
}
else if (oneOfValue.IsT1)
{
var num = oneOfValue.AsT1;
Console.WriteLine($"Number: {num}");
}
After (Code Fix Applied)β
// β
Using generated adapter
OneOf<string, int> oneOfValue = GetValue();
var result = MyUnion.FromOneOf(oneOfValue);
result.Match(
case1: str => Console.WriteLine($"String: {str}"),
case2: num => Console.WriteLine($"Number: {num}")
);
π¨ IDE Integrationβ
Visual Studioβ
- Position cursor on the warning squiggle
- Click the light bulb icon or press Ctrl+.
- Select the code fix from the menu
- Press Enter to apply
Preview changes: Press Ctrl+., then use arrow keys to navigate, and press Space to preview before applying.
JetBrains Riderβ
- Position cursor on the warning
- Press Alt+Enter (Windows/Linux) or Option+Enter (Mac)
- Select "Fix all issues of this type in file" for bulk fixes
- Press Enter to apply
Preview: Rider shows a diff preview before applying.
VS Codeβ
- Position cursor on the diagnostic
- Click the light bulb or press Ctrl+. (Cmd+. on Mac)
- Select the code fix
- Press Enter
Requires: C# extension (OmniSharp)
π Batch Code Fixesβ
Apply fixes to multiple locations simultaneously.
Fix All in Documentβ
Visual Studio: Ctrl+. β "Fix all occurrences in Document"
Rider: Alt+Enter β "Fix all issues of this type in file"
Fix All in Project/Solutionβ
Visual Studio:
- Error List β Right-click diagnostic
- "Fix all occurrences in Project/Solution"
Rider:
- Solution-wide analysis (Alt+` β Enable)
- Inspection Results β Right-click β "Fix all"
Example: Bulk Fixβ
Suppose you add a new case to an existing union:
[GenerateUnion]
public partial record Status
{
public static partial Status Pending();
public static partial Status Active();
// β New case added
public static partial Status Suspended();
}
Now all existing Match() calls show UG1001 warnings. Apply batch fix:
Before (20 locations):
status.Match(
pending: () => "Pending",
active: () => "Active"
);
After (one action fixes all 20):
status.Match(
pending: () => "Pending",
active: () => "Active",
suspended: () => throw new NotImplementedException()
);
Then manually implement each new suspended handler.
βοΈ Customizing Code Fix Behaviorβ
Change Default Handlerβ
By default, missing handlers use throw new NotImplementedException(). Customize this:
Option 1: Use EditorConfig Snippetsβ
# .editorconfig
[*.cs]
# Default TODO comment
csharp_code_fix_missing_case_handler = // TODO: Implement {0} case
Custom handler templates are not yet supported but are planned for future releases.
Option 2: Configure Post-Fix Behaviorβ
After applying a code fix:
- Use Find and Replace to update all
NotImplementedExceptioninstances - Or manually implement each handler
// After bulk fix, use regex find-replace:
// Find: throw new NotImplementedException\(\)
// Replace: HandleUnknownCase()
Disable Specific Code Fixesβ
In .editorconfig:
# Disable code fix for UG3001
dotnet_code_fix.UG3001.enabled = false
Or suppress the underlying diagnostic:
dotnet_diagnostic.UG3001.severity = none
π‘ Best Practicesβ
1. Apply Fixes Incrementally for New Casesβ
When adding a new union case:
// 1. Add the case
public static partial Result Timeout();
// 2. Build to see all UG1001/UG002 warnings
// 3. Review each location and decide:
// - Should this handle Timeout explicitly?
// - Can Timeout be grouped with another case?
// 4. Apply fixes one by one, not in bulk
2. Replace NotImplementedException Immediatelyβ
// β Don't leave this in production code
result.Match(
success: v => v,
error: e => throw new NotImplementedException()
);
// β
Replace with actual logic
result.Match(
success: v => v,
error: e => LogAndReturnDefault(e)
);
3. Use TODO Commentsβ
If you can't implement immediately:
result.Match(
success: v => v,
error: e =>
{
// TODO: Implement proper error handling for production
throw new NotImplementedException();
}
);
Add to your CI pipeline:
# Fail build if TODO comments exist
dotnet build && ! grep -r "TODO.*NotImplementedException" src/
4. Review Discard Arms Carefullyβ
Code fix might suggest discard arms:
// β
Acceptable when remaining cases should be treated identically
var isError = result switch
{
{ IsSuccess: true } => false,
_ => true // All errors treated as true
};
// β οΈ Review carefully - might hide future bugs
var message = result switch
{
{ IsSuccess: true } s => s.Value,
_ => "Error" // Loses specific error information!
};
π Troubleshootingβ
Code Fix Not Appearingβ
Check:
- Analyzer package is installed
- Build the project to ensure diagnostics are current
- Cursor is positioned on the diagnostic
- IDE extension for C# is up to date
Verify:
dotnet list package | grep UnionGenerator.Analyzers
Code Fix Generates Invalid Codeβ
Report an issue: GitHub Issues
Include:
- Original code snippet
- Diagnostic ID
- Generated code after fix
- Expected result
Batch Fix Doesn't Workβ
Limitations:
- Batch fixes only work within the same diagnostic ID
- Cross-file fixes may require project rebuild
- Some IDEs limit batch operations
Workaround:
- Apply fix in one file
- Build project
- Repeat for next file
π Technical Detailsβ
How Code Fixes Workβ
- Trigger: Analyzer reports diagnostic (UG1001, UG002, etc.)
- Registration: Code fix provider registers available fixes
- User Action: User invokes light bulb menu
- Code Action: Provider generates new code using Roslyn APIs
- Application: IDE applies text changes to document
Performance Considerationsβ
- Code fixes compute on-demand (not on every keystroke)
- Lazy evaluation until light bulb is opened
- Minimal performance impact on IDE
- Complex fixes may take 100-500ms to compute
Supported Scenariosβ
Code fixes handle:
- β Switch expressions and statements
- β Match() method calls
- β Generic union types
- β Nested unions
- β Partial matches with default arms
Not yet supported:
- β If-else chain refactoring (UG1001 in if statements)
- β Custom handler templates
- β Smart case grouping suggestions
π Related Topicsβ
- Pattern Matching Analyzers - Diagnostics that trigger fixes
- Factory Method Analyzers - Union definition validation
- Best Practices - Union design patterns
π Complete Exampleβ
// Initial union
[GenerateUnion]
public partial record TaskStatus
{
public static partial TaskStatus NotStarted();
public static partial TaskStatus InProgress(int percentComplete);
public static partial TaskStatus Completed(DateTime completedAt);
}
// Code using the union
public string GetStatusMessage(TaskStatus status)
{
return status.Match(
notStarted: () => "Not started yet",
inProgress: percent => $"{percent}% complete",
completed: dt => $"Done at {dt:g}"
);
}
// β Later: Add new case
public static partial TaskStatus Failed(string error);
// β οΈ Now GetStatusMessage shows UG1001 warning
// Apply code fix (Ctrl+.)
public string GetStatusMessage(TaskStatus status)
{
return status.Match(
notStarted: () => "Not started yet",
inProgress: percent => $"{percent}% complete",
completed: dt => $"Done at {dt:g}",
failed: error => throw new NotImplementedException() // β
Added by code fix
);
}
// β
Manually replace NotImplementedException
public string GetStatusMessage(TaskStatus status)
{
return status.Match(
notStarted: () => "Not started yet",
inProgress: percent => $"{percent}% complete",
completed: dt => $"Done at {dt:g}",
failed: error => $"Failed: {error}" // β
Implemented
);
}
π Next Stepsβ
- Learn about pattern matching analyzers that trigger code fixes
- Explore ASP.NET Core analyzers for web-specific refactorings
- Review best practices for union design