Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
Foundational design principles for .NET applications. Covers each SOLID principle with concrete C# anti-patterns and
fixes, plus DRY guidance with nuance on when duplication is acceptable. These principles guide class design, interface
contracts, and dependency management across all .NET project types.
Scope
SOLID principles with C# anti-patterns and fixes
DRY guidance and when duplication is acceptable
SRP compliance tests and class design heuristics
Interface segregation and dependency inversion patterns
Out of scope
Architectural patterns (vertical slices, request pipelines, caching) -- see [skill:dotnet-architecture-patterns]
DI container mechanics (registration, lifetimes, keyed services) -- see [skill:dotnet-csharp-dependency-injection]
Code smells and anti-pattern detection -- see [skill:dotnet-csharp-code-smells]
Cross-references: [skill:dotnet-architecture-patterns] for clean architecture and vertical slices,
[skill:dotnet-csharp-dependency-injection] for DI registration patterns and lifetime management,
[skill:dotnet-csharp-code-smells] for anti-pattern detection, [skill:dotnet-csharp-coding-standards] for naming and
style conventions.
Single Responsibility Principle (SRP)
A class should have only one reason to change. Apply the "describe in one sentence" test: if you cannot describe what a
class does in one sentence without using "and" or "or", it likely violates SRP.
// WRONG -- ReadOnlyFileStorage violates the base contract by
// throwing on a method the base type supports
public
class
ReadOnlyFileStorage
FileStorage
publicoverride Stream OpenRead(string path)
if
throw
new
"Cannot open files in read-only mode"
return
base
// Surprise: callers expecting FileStorage behavior get exceptions
### Anti-Pattern: Collection Covariance Pitfall
// WRONG -- List<T> is not covariant; this compiles but causes runtime issues
new
// Compile error (correctly)
// However, arrays ARE covariant in C# -- this compiles but throws at runtime:
new
10
0
new
// ArrayTypeMismatchException at runtime!
### Fix: Use Covariant Interfaces
// IEnumerable<out T> and IReadOnlyList<out T> are covariant
new
// Safe -- read-only
new
// Safe
// When you need mutability, keep the concrete type
new
"Rex"
new
"Buddy"
// Pass to covariant parameter
voidProcessAnimals(IReadOnlyList<Animal> animals)
foreach
var
in
### LSP Compliance Checklist
do
not
throw
new
base
not
Overrides donotaddpreconditions (e.g., null checks the base does not require)
- Overrides donot weaken postconditions (e.g., returning nullwhenbase guarantees non-null)
- Behavioral contracts are preserved: if `ICollection.Add` succeeds on the base, it must succeed on the derived type
---
## Interface Segregation Principle (ISP)
Clients should not be forced to depend on methods they donot use. Prefer narrow, role-specific interfaces over wide "header" interfaces.
### Anti-Pattern: Header Interface
```csharp
// WRONG -- IWorker forces all implementations to support every capabilitypublicinterface IWorker
Task DoWorkAsync(CancellationToken ct)
voidClockIn()
voidClockOut()
Task<decimal> CalculatePayAsync()
voidRequestTimeOff(DateRange range)
Task SubmitExpenseAsync(Expense expense)
// ContractWorker does not clock in/out or request time off
public
class
ContractWorker
IWorker
public Task DoWorkAsync(CancellationToken ct)
/* ... */
publicvoidClockIn()
throw
new
// ISP violation
publicvoidClockOut()
throw
new
// ISP violation
public Task<decimal> CalculatePayAsync()
/* ... */
publicvoidRequestTimeOff(DateRange range)
throw
new
// ISP violation
public Task SubmitExpenseAsync(Expense expense)
throw
new
// ISP violation
### Fix: Role Interfaces
public
interface
IWorkPerformer
Task DoWorkAsync(CancellationToken ct)
public
interface
ITimeTrackable
voidClockIn()
voidClockOut()
public
interface
IPayable
Task<decimal> CalculatePayAsync()
public
interface
ITimeOffEligible
voidRequestTimeOff(DateRange range)
// FullTimeEmployee implements all applicable interfaces
public
sealed
class
FullTimeEmployee
IWorkPerformer
ITimeTrackable
IPayable
ITimeOffEligible
public Task DoWorkAsync(CancellationToken ct)
/* ... */
publicvoidClockIn()
/* ... */
publicvoidClockOut()
/* ... */
public Task<decimal> CalculatePayAsync()
/* ... */
publicvoidRequestTimeOff(DateRange range)
/* ... */
// ContractWorker only implements what it needs
public
sealed
class
ContractWorker
IWorkPerformer
IPayable
public Task DoWorkAsync(CancellationToken ct)
/* ... */
public Task<decimal> CalculatePayAsync()
/* ... */
### Practical .NET ISP
interface
your
method
actually
needs
csharp
// WRONG -- requires IList<T> but only reads
public
decimal
CalculateTotal
IList
OrderLine
lines
// RIGHT -- accepts IReadOnlyList<T> since it only reads
abstractinfrastructure (database, email, file system, HTTP clients)
- **DO** abstract cross-cutting concerns (logging is already abstracted via `ILogger<T>`)
- **DO NOT** abstract simple value objects, DTOs, orinternal implementation details
- **DO NOT** create `IFoo`/`Foo` pairs for every class -- only abstractwhere substitution adds value (testing, multiple implementations, or anticipated change)
---
## DRY (Don't Repeat Yourself)
Every piece of knowledge should have a single, authoritative representation. But DRY is about knowledge duplication, not code duplication.
### When to Apply DRY
Apply DRY when two pieces of code represent the **same concept** and must change together:
```csharp
// WRONG -- tax rate duplicated across two services
public sealed class InvoiceService
{
public decimal CalculateTax(decimal amount) => amount * 0.08m;
}
public sealed class QuoteService
{
public decimal EstimateTax(decimal amount) => amount * 0.08m;
}
// RIGHT -- single source of truth
public static class TaxRates
{
public const decimal StandardRate = 0.08m;
}
```text
### Rule of Three
Do not abstract prematurely. Wait until you see the same pattern three times before extracting a shared abstraction:
1. **First occurrence** -- write it inline
2. **Second occurrence** -- note the duplication but keep it (the two usages may diverge)
3. **Third occurrence** -- extract a shared method, class, or utility
### When Duplication Is Acceptable
Not all code similarity represents knowledge duplication:
```csharp
// These look similar but represent DIFFERENT business concepts
// They will evolve independently -- DO NOT merge them
public sealed class CustomerValidator
{
public bool IsValid(Customer customer) =>
!string.IsNullOrEmpty(customer.Name) &&
!string.IsNullOrEmpty(customer.Email);
}
public sealed class SupplierValidator
{
public bool IsValid(Supplier supplier) =>
!string.IsNullOrEmpty(supplier.Name) &&
!string.IsNullOrEmpty(supplier.ContactEmail);
}
```text
**Acceptable duplication scenarios:**
- Test setup code that looks similar across test classes (coupling tests to shared helpers makes them fragile)
- DTOs for different API versions (V1 and V2 may share fields now but diverge later)
- Configuration for different environments (dev and prod configs that happen to be similar today)
- Mapping code between layers (coupling layers to share mappers defeats the purpose of separate layers)
### Abstracting Shared Behavior
When you do extract, prefer composition over inheritance:
```csharp
// Prefer: composition via a shared utility
public static class StringValidation
{
public static bool IsNonEmpty(string? value) =>
!string.IsNullOrWhiteSpace(value);
}
// Over: inheritance via a base class
// (couples validators to a shared base, harder to test independently)
```text
---
## Applying the Principles Together
### Decision Guide
| Symptom | Likely Violation | Fix |
|---|---|---|
| Class described with "and" | SRP | Split into focused classes |
| Modifying existing code to add features | OCP | Use strategy/plugin pattern |
| `NotSupportedException` in overrides | LSP | Redesign hierarchy or use composition |
| Empty/throwing interface methods | ISP | Split into role interfaces |
| `new` keyword for dependencies | DIP | Inject via constructor |
| Magic numbers/strings in multiple files | DRY | Extract constants or config |
| Copy-pasted code blocks (3+) | DRY | Extract shared method |
### SRP Compliance Test
For each class, answer these questions:
1. **One-sentence test:** Can you describe the class's purpose in one sentence without "and"or"or"?
2. **Change-reason test:** List all reasons thisclass might need to change. If more than one, consider splitting.
3. **Dependency count test:** Does the constructor take more than 3-4 dependencies? High parameter counts often signal multiple responsibilities.
---
## Agent Gotchas
1. **Do not create `IFoo`/`Foo` pairs for every class.** DIP calls for abstractions at module boundaries (infrastructure, external services), notfor every internalclass. Unnecessary interfaces add indirection without valueand clutter the codebase.
2. **Do not merge similar-looking code from different bounded contexts.** Two validators or DTOs that look alike but serve different business concepts should remain separate. Premature DRY creates coupling between concepts that evolve independently.
3. **Do not use inheritance to share behavior between unrelated types.** Prefer composition (injecting a shared service orusing extension methods) over inheriting from a common baseclass. Inheritance creates tight coupling and makes LSP violations more likely.
4. **Fat controllers and god classes are SRP violations.** When generating endpoint handlers, keep them thin -- delegate to dedicated services for validation, business logic, and persistence. Apply the "one sentence" test to each class.
5. **Switch statements on type discriminators violate OCP.** Replace them withpolymorphism (strategy pattern, interface dispatch) so new types can be added without modifying existing code.
6. **Array covariance in C# isunsafe.** `Animal[] animals