Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
Design-time principles for creating public .NET APIs that are intuitive, consistent, and forward-compatible. Covers
naming conventions for API surface, parameter ordering, return type selection, error reporting strategies, extension
points, and wire compatibility for serialized types. This skill addresses the design decisions that make APIs
compatible and usable in the first place, before enforcement tooling gets involved.
Version assumptions: .NET 8.0+ baseline. Examples use modern C# features (primary constructors, collection
expressions) where appropriate.
Scope
Naming conventions for public API types, methods, and parameters
Parameter ordering and overload progression
Return type selection (nullable, IReadOnlyList, IAsyncEnumerable, ValueTask)
Error reporting strategies (exceptions, Try pattern, result objects)
Binary/source compatibility enforcement and tooling -- see [skill:dotnet-library-api-compat]
PublicApiAnalyzers, Verify snapshots, and CI validation of API surface -- see [skill:dotnet-api-surface-validation]
General C# naming conventions and file layout -- see [skill:dotnet-csharp-coding-standards]
HTTP API versioning and URL design -- see [skill:dotnet-api-versioning]
NuGet packaging and SemVer mechanics -- see [skill:dotnet-nuget-authoring]
Cross-references: [skill:dotnet-library-api-compat] for compatibility enforcement, [skill:dotnet-api-surface-validation]
for CI detection, [skill:dotnet-csharp-coding-standards] for general naming rules, [skill:dotnet-api-versioning] for
HTTP API versioning, [skill:dotnet-nuget-authoring] for SemVer and packaging.
Naming Conventions for API Surface
Type Naming
Follow the .NET Framework Design Guidelines naming patterns for public API types:
Type Kind
Suffix Pattern
Example
Base class
Base suffix only for abstract base types
ValidatorBase
Interface
I prefix
IWidgetFactory
Exception
Exception suffix
WidgetNotFoundException
Attribute
Attribute suffix
RequiredPermissionAttribute
Event args
EventArgs suffix
WidgetCreatedEventArgs
Options/config
Options suffix
WidgetServiceOptions
Builder
Builder suffix
WidgetBuilder
Method Naming
Pattern
Convention
Example
Synchronous
Verb or verb phrase
Calculate(), GetWidget()
Asynchronous
Async suffix
CalculateAsync(), GetWidgetAsync()
Boolean query
Is/Has/Can prefix
IsValid(), HasPermission()
Try pattern
Try prefix, out parameter
TryGetWidget(int id, out Widget widget)
Factory
Create prefix
CreateWidget(), CreateWidgetAsync()
Conversion
To/From prefix
ToDto(), FromEntity()
Avoid Abbreviations in Public API
Spell out words in public APIs even if internal code uses abbreviations. Public APIs are consumed by developers who may
not share the team's domain shorthand:
// WRONG -- abbreviations in public surfacepublic IReadOnlyList<TxnResult> GetRecentTxns(int cnt);
// CORRECT -- spelled out for claritypublic IReadOnlyList<TransactionResult> GetRecentTransactions(int count);
```text
---
## Parameter Ordering
Consistent parameter ordering reduces cognitive load and enables fluent usage patterns across an API surface.
### Standard Order1. **Target/subject** -- the primary entity being operated on2. **Required parameters** -- essential inputs without defaults
3. **Optional parameters** -- inputs with sensible defaults
4. **Cancellation token** -- always last (convention enforced by CA1068)
```csharp
// Consistent ordering across the API surfacepublic Task<Widget> GetWidgetAsync(int widgetId, // 1. Target
WidgetOptions options, // 2. Required
bool includeHistory = false, // 3. Optional
CancellationToken cancellationToken = default); // 4. Always lastpublic Task<Widget> UpdateWidgetAsync(int widgetId, // 1. Target
WidgetUpdateRequest request, // 2. Required
validateFirst = , // Optional
CancellationToken cancellationToken = );
```text
Design overloads a progression simple to detailed. Each overload should to the next more specific one:
```csharp
=> GetWidgetAsync(widgetId, WidgetOptions.Default, cancellationToken);
;
```text
---
| Scenario | Return Type | Rationale |
| --------------------------------- | -------------------------------------- | ------------------------------------------------ |
| Single entity, always exists | `Widget` | Throw found |
| Single entity, may exist | `Widget?` | Nullable reference type communicates optionality |
| Collection, possibly empty | `IReadOnlyList<Widget>` | Immutable, indexable, communicates no mutation |
| Streaming results | `IAsyncEnumerable<Widget>` | Avoids buffering entire result |
| Operation result detail | `Result<Widget>` / discriminated union | Rich error info without exceptions |
| Void | `Task` | Never ` ` except handlers |
| Frequently synchronous completion | `ValueTask<Widget>` | Avoids Task allocation cache hits |
```csharp
;
;
;
```text
Use the Try pattern operations that have a common, non-exceptional failure mode:
```csharp
;
Task<Widget?> TryGetWidgetAsync( widgetId,
CancellationToken cancellationToken = );
```text
---
Design exception types that enable callers to at the right granularity:
```csharp
:
{
{ }
{ }
}
:
{
WidgetId { ; }
=> WidgetId = widgetId;
}
:
{
IReadOnlyList<> Errors { ; }
=> Errors = errors;
}
```text
| Approach | When to Use |
| ---------------------------- | ------------------------------------------------------------------------- |
| Throw exception | Unexpected failures, programming errors, infrastructure failures |
| Return `` / `` | a normal,
{
ArgumentException.ThrowIfNullOrWhiteSpace(name);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(price);
Widget(name, price);
}
```text
Use `ArgumentException.ThrowIfNullOrWhiteSpace` (.NET +) `ArgumentOutOfRangeException.ThrowIfNegativeOrZero` (.NET
+) instead of manual checks ` ArgumentNullException(...)`.
{
;
}
{
Func<Widget, CancellationToken, ValueTask>? OnWidgetCreated { ; ; }
}
{
List<IWidgetValidator> _validators = [];
{
_validators.Add(validator);
;
}
{
_validators.Add( DelegateValidator(validator));
;
}
=> (_validators);
}
```text
| Guideline | Rationale |
| ---------------------------------------------------------------------- | ------------------------------------------------------------ |
| Place extensions the same | `` |
| `` `` | |
| | |
| ' `` | `<>` ``; avoids polluting IntelliSense |
---
; old clients ignore field |
| Add member at the end | Existing serialized values map to existing members |
| Rename property `[JsonPropertyName]` annotation | Wire name stays the same |
| Change | Impact |
| --------------------------------------------------- | ------------------------------------------------------------ |
| ; old clients send unrecognized fields |
| Change property type | Deserialization failure silent data loss |
|
{
[]
Id { ; ; }
[]
Name { ; ; }
[]
? Category { ; ; }
[]
[]
Priority { ; ; }
}
```json
```csharp
[]
WidgetStatus
{
Draft,
Active,
Archived
}
WidgetPriority
{
Low = ,
Medium = ,
High =
}
```text
---
Before shipping a API, verify each concern:
**Naming** -- follows .NET naming conventions, no abbreviations, consistent rest of API surface
**Parameters** -- ordered (target, , optional, CancellationToken), no more than ~ parameters (use options
complex APIs)
**Return types** --
bool
true
3.
default
// 4. Always last
### Overload Progression
as
from
delegate
// Simple -- sensible defaults
public Task<Widget> GetWidgetAsync(int widgetId,
CancellationToken cancellationToken = default)
// Detailed -- full control
public Task<Widget> GetWidgetAsync(int widgetId,
WidgetOptions options,
CancellationToken cancellationToken = default)
## Return Type Selection
### When to Return What
if
not
not
set
with
with
async
async
void
event
on
### Prefer IReadOnlyList Over IEnumerable for Materialized Collections
// WRONG -- caller does not know if result is materialized or lazy
expected outcome (query patterns) |
| Try pattern (`bool` + `out`) | Parsing or validation where failure is common and synchronous |
| Result object | Multiple failure modes that callers need to distinguish without try/catch |
### Argument Validation
Validate public API entry points immediately andthrow the standard .NET exceptions:
```csharp
public Widget CreateWidget(string name, decimal price)
// Proceed with creation
return
new
8
and
8
null
with
throw
new
These throw helpers are optimized by the
JIT (no delegate allocation, better inlining).
---
## Extension Points
### Designing for Extensibility Without Inheritance
Prefer composition and interfaces over class inheritance for extension points:
```csharp
// GOOD -- interface-based extension pointpublicinterface IWidgetValidator
// GOOD -- string serialization is rename-safe and human-readable
JsonConverter(typeof(JsonStringEnumConverter))
public
enum
// RISKY -- integer serialization breaks when members are reordered or inserted
// Only use when wire format size is critical and members are append-only
public
enum
0
1
2
// New members MUST go at the end with explicit values
## API Design Checklist
new
public
1.
with
2.
required
5
object
for
3.
appropriate for the scenario (nullable for optional, IReadOnlyList for collections,
Task/ValueTask forasync)
4. **Error handling** -- clear exception types, argument validation at entry points, Try pattern where failure is
expected
5. **Extension points** -- interfaces or delegates, notvirtual methods on concrete classes
6. **Wire safety** -- serialized types use explicit property names, additive-only evolution, enum strategy documented
7. **Compatibility** -- changes reviewed against [skill:dotnet-library-api-compat] rules before release
---
## Agent Gotchas
1. **Do not use abbreviations inpublic API names** -- spell out words even wheninternal code uses shorthand. Public
APIs are consumed by developers outside the team who donot share the domain vocabulary.
2. **Do not place CancellationToken before optional parameters** -- CA1068 enforces CancellationToken as the last
parameter. Placing it earlier breaks the standard ordering convention and triggers analyzer warnings.
3. **Do notreturn mutable collections frompublic APIs** -- return `IReadOnlyList<T>` or `IReadOnlyCollection<T>`
instead of `List<T>` or `IList<T>`. Mutable return types allow callers to corrupt internal state.
4. **Do not change serialized property names without `[JsonPropertyName]` annotations** -- renaming a C# property
without preserving the wire name breaks all existing serialized data and API clients.
5. **Do notaddrequired parameters to existing public methods** -- thisis a source-breaking change. Add a new overload
or use optional parameters with defaults instead.
6. **Do not use `asyncvoid` in API surface** -- return `Task` or `ValueTask`. The only valid `asyncvoid` is framework
event handlers. See [skill:dotnet-csharp-async-patterns].
7. **Do not design exception hierarchies without a base library exception** -- callers need a single catch point for all
library errors. Always provide a base exception type that specific exceptions derive from.
8. **Do not put extension methods in the `System` namespace** -- namespace pollution affects every filein every
consumer project. Use the library's own namespaceor a dedicated `.Extensions` sub-namespace.
---
## Prerequisites
- .NET 8.0+ SDK
- Familiarity with C# naming conventions (see [skill:dotnet-csharp-coding-standards])
- Understanding of binary/source compatibility concepts (see [skill:dotnet-library-api-compat])
- System.Text.Json for wire compatibility examples
---
## References
- [Framework Design Guidelines (Microsoft Learn)](https://learn.microsoft.com/dotnet/standard/design-guidelines/)
- [API design best practices (Microsoft REST API Guidelines)](https://github.com/microsoft/api-guidelines)
- [Breaking changes reference](https://learn.microsoft.com/dotnet/core/compatibility/categories)
- [System.Text.Json serialization](https://learn.microsoft.com/dotnet/standard/serialization/system-text-json/overview)
- [CA1068: CancellationToken parameters must come last](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1068)