Cocoar.Capabilities
0.4.0-alpha.0
See the version list below for details.
dotnet add package Cocoar.Capabilities --version 0.4.0-alpha.0
NuGet\Install-Package Cocoar.Capabilities -Version 0.4.0-alpha.0
<PackageReference Include="Cocoar.Capabilities" Version="0.4.0-alpha.0" />
<PackageVersion Include="Cocoar.Capabilities" Version="0.4.0-alpha.0" />
<PackageReference Include="Cocoar.Capabilities" />
paket add Cocoar.Capabilities --version 0.4.0-alpha.0
#r "nuget: Cocoar.Capabilities, 0.4.0-alpha.0"
#:package Cocoar.Capabilities@0.4.0-alpha.0
#addin nuget:?package=Cocoar.Capabilities&version=0.4.0-alpha.0&prerelease
#tool nuget:?package=Cocoar.Capabilities&version=0.4.0-alpha.0&prerelease
Cocoar.Capabilities
A general-purpose capabilities system for .NET that enables type-safe, composable capability attachment to any object. Perfect for cross-project extensibility without circular dependencies.
๐ฏ What is it?
Cocoar.Capabilities allows you to attach typed capabilities to any object and retrieve them later in a type-safe manner. Think of it as a strongly-typed, high-performance property bag that enables cross-project extensibility patterns.
This library implements Capability Composition: a component/extension-object style where a subject carries a typed, immutable bag of capabilities (policies/behaviors). It's composition-over-inheritance, with exact-type lookups and cross-project contributions.
Architecture Pattern
The design maps to well-established patterns:
- Subject โ Host object (any class)
- Capability โ Component/extension/role (behavior or policy)
- CapabilityBag โ Extension registry on the host
AddAs<TContract>()
โ Register under an extension interface- Primary marker โ Ensures exactly one "role of kind Primary" is present
Related Patterns: Extension Object, Role Object, Component-Based Design, Strategy/Policy, Composition over Inheritance
Key Benefits
- ๐ Type Safe: Compile-time guarantees for capability-subject relationships
- โก Zero Allocation: Optimized hot paths with
Array.Empty<T>()
and type-safe casting - ๐งต Thread Safe: Immutable by design - no locks needed
- ๐ Extensible: Any project can define capabilities for any subject type
- ๐ฆ Lightweight: Zero dependencies, AOT-friendly
๐ Quick Start
Installation
# When published to NuGet
dotnet add package Cocoar.Capabilities
# For now, reference the project directly
<ProjectReference Include="path/to/Cocoar.Capabilities/Cocoar.Capabilities.csproj" />
Basic Usage
using Cocoar.Capabilities;
// 1. Define capabilities for your domain
public record LogLevelCapability<T>(LogLevel Level) : ICapability<T>;
public record CacheCapability<T>(TimeSpan Ttl) : ICapability<T>;
// 2. Create a capability bag
var userService = new UserService();
var bag = Composer.For(userService)
.Add(new LogLevelCapability<UserService>(LogLevel.Debug))
.Add(new CacheCapability<UserService>(TimeSpan.FromMinutes(5)))
.Build();
// 3. Use capabilities later
if (bag.TryGet<LogLevelCapability<UserService>>(out var logLevel))
{
logger.SetLevel(logLevel.Level);
}
if (bag.TryGet<CacheCapability<UserService>>(out var cache))
{
ConfigureCache(cache.Ttl);
}
๐ Core Concepts
1. Capabilities
A capability represents a piece of functionality or configuration that can be attached to a subject:
// Generic capability - works with any subject type T
public record MyCapability<T>(string Value) : ICapability<T>;
// Specific capability - only works with UserService
public record UserCapability(int UserId) : ICapability<UserService>;
2. Subjects
A subject is any object that can have capabilities attached. No special interfaces or base classes required:
// Any class can be a subject
public class UserService { }
public class DatabaseConfig { }
public class PaymentProcessor { }
3. Capability Bags
A capability bag is an immutable container that stores capabilities for a specific subject:
var bag = Composer.For(myObject)
.Add(new SomeCapability<MyObject>("value"))
.Add(new AnotherCapability<MyObject>(42))
.Build(); // Immutable from this point
๐ง API Reference
Building Capability Bags
// Create a builder for any object
var builder = Composer.For(myObject);
// Add capabilities by concrete type
builder.Add(new MyCapability<MyObject>("value"));
// Add capabilities by interface/contract (for exact-type retrieval)
builder.AddAs<IMyCapability<MyObject>>(new ConcreteCapability<MyObject>());
// Build immutable bag (one-shot operation)
var bag = builder.Build();
// โ This throws - builder is unusable after Build()
var bag2 = builder.Build(); // InvalidOperationException
Retrieving Capabilities
var bag = /* ... built bag ... */;
// Try to get a capability (safe)
if (bag.TryGet<MyCapability<MyObject>>(out var capability))
{
Console.WriteLine(capability.Value);
}
// Get required capability (throws if missing)
var required = bag.GetRequired<MyCapability<MyObject>>();
// Get all capabilities of a type
var allCapabilities = bag.GetAll<MyCapability<MyObject>>();
// Check if capability exists
bool exists = bag.Contains<MyCapability<MyObject>>();
// Count capabilities of a type
int count = bag.Count<MyCapability<MyObject>>();
// Total capability count across all types
int total = bag.TotalCapabilityCount;
Convenience Extensions
// Execute action only if capability exists
bag.Use<MyObject, MyCapability<MyObject>>(cap =>
{
Console.WriteLine($"Found: {cap.Value}");
});
// Transform capability value (returns default if missing)
var result = bag.Transform<MyObject, MyCapability<MyObject>, string>(cap =>
cap.Value.ToUpper());
๐ฏ Real-World Examples
Configuration System
// Define configuration-specific capabilities
public record ExposeAsCapability<T>(Type ContractType) : ICapability<T>;
public record SingletonLifetimeCapability<T> : ICapability<T>;
public record HealthCheckCapability<T>(string Name) : ICapability<T>;
// Create configuration with capabilities
var dbConfig = new DatabaseConfig { ConnectionString = "..." };
var configBag = Composer.For(dbConfig)
.Add(new ExposeAsCapability<DatabaseConfig>(typeof(IDbConfig)))
.Add(new SingletonLifetimeCapability<DatabaseConfig>())
.Add(new HealthCheckCapability<DatabaseConfig>("database"))
.Build();
// Process capabilities in your DI registration code
if (configBag.Contains<SingletonLifetimeCapability<DatabaseConfig>>())
{
services.AddSingleton(configBag.Subject);
}
foreach (var expose in configBag.GetAll<ExposeAsCapability<DatabaseConfig>>())
{
services.AddSingleton(expose.ContractType, _ => configBag.Subject);
}
Plugin Architecture
// Plugin can automatically contribute capabilities
public class SecurityPlugin<T> : ICapabilityPlugin<T>
{
public void ContributeCapabilities(CapabilityBagBuilder<T> builder)
{
builder.Add(new AuthenticationCapability<T>());
builder.Add(new AuthorizationCapability<T>("DefaultPolicy"));
}
}
// Host discovers and applies plugins
var bag = PluginSystem.CreateWithPlugins(myObject)
.Add(new CustomCapability<MyObject>()) // Your own capabilities
.Build();
Web Framework Integration
// Declarative endpoint configuration
var controllerBag = Composer.For(new UsersController())
.Add(new RouteCapability<UsersController>("/api/users"))
.Add(new AuthorizeCapability<UsersController>("AdminPolicy"))
.Add(new RateLimitCapability<UsersController>(100))
.Build();
// Framework processes capabilities automatically
ProcessWebCapabilities(controllerBag);
๐ See Advanced Examples for complete implementations including:
- Primary Capability Pattern - Extensible "exactly one" constraint system
- Cross-Assembly Plugin Architecture - Automatic capability discovery
- Complete Configuration System - Full DI integration with validation and health checks
- Web Framework Integration - Declarative routing, auth, caching, and rate limiting
- Event-Driven Architecture - Handler orchestration with ordering and error handling
โก Advanced Features
Ordered Capabilities
Capabilities can implement IOrderedCapability
for predictable ordering:
public record PriorityCapability<T>(int Priority, string Name) : ICapability<T>, IOrderedCapability
{
public int Order => Priority; // Lower values run first
}
var bag = Composer.For(myObject)
.Add(new PriorityCapability<MyObject>(100, "Last"))
.Add(new PriorityCapability<MyObject>(1, "First"))
.Add(new PriorityCapability<MyObject>(50, "Middle"))
.Build();
var ordered = bag.GetAll<PriorityCapability<MyObject>>();
// Returns: ["First", "Middle", "Last"]
Contract-Based Retrieval
Use AddAs<T>()
when you need to retrieve capabilities by interface:
public interface IValidationCapability<T> : ICapability<T>
{
bool IsValid { get; }
}
public record EmailValidationCapability<T> : IValidationCapability<T>
{
public bool IsValid => true;
}
var bag = Composer.For(myObject)
// Register concrete type under interface contract
.AddAs<IValidationCapability<MyObject>>(new EmailValidationCapability<MyObject>())
.Build();
// Retrieve by interface
var validator = bag.GetRequired<IValidationCapability<MyObject>>();
Cross-Project Extensibility
Different projects can add capabilities to the same subject without dependencies:
// Core.dll - Defines the subject
namespace MyApp.Core
{
public class UserService { }
}
// DI.dll - Adds DI capabilities
namespace MyApp.DI
{
public record SingletonCapability<T> : ICapability<T>;
}
// Web.dll - Adds web capabilities
namespace MyApp.Web
{
public record RouteCapability<T>(string Template) : ICapability<T>;
}
// Composition.dll - Composes everything
var userService = new UserService();
var bag = Composer.For(userService)
.Add(new SingletonCapability<UserService>()) // From DI.dll
.Add(new RouteCapability<UserService>("/api/users")) // From Web.dll
.Build();
๐จ Important Notes
Exact Type Matching
The system uses exact type matching - it only finds capabilities registered under the exact same type:
// โ This won't work
builder.Add(new ConcreteCapability<T>());
bag.TryGet<ICapability<T>>(out _); // Returns false!
// โ
Use AddAs<T> for interface retrieval
builder.AddAs<ICapability<T>>(new ConcreteCapability<T>());
bag.TryGet<ICapability<T>>(out _); // Returns true!
Builder Lifecycle
Builders are single-use - they become unusable after Build()
:
var builder = Composer.For(myObject);
var bag1 = builder.Build(); // โ
Works
var bag2 = builder.Build(); // โ Throws InvalidOperationException
Thread Safety
- Capability Bags: Thread-safe (immutable)
- Builders: NOT thread-safe (single-threaded use only)
var bag = builder.Build(); // Thread-safe from this point
// โ
Safe to use from multiple threads
Task.Run(() => bag.TryGet<MyCapability<T>>(out _));
Task.Run(() => bag.GetAll<MyCapability<T>>());
๐ง Integration Patterns
For Library Authors
When creating a library that uses Cocoar.Capabilities:
- Define your domain capabilities:
namespace MyLibrary.Capabilities
{
public record MyLibraryCapability<T>(string Setting) : ICapability<T>;
public record AnotherCapability<T>(int Value) : ICapability<T>;
}
- Accept capability bags in your APIs:
public void Configure<T>(ICapabilityBag<T> capabilityBag)
{
// Process capabilities to configure behavior
if (capabilityBag.TryGet<MyLibraryCapability<T>>(out var cap))
{
// Apply configuration
}
}
- Provide fluent builders (optional):
public static class MyLibraryExtensions
{
public static CapabilityBagBuilder<T> WithMyFeature<T>(
this CapabilityBagBuilder<T> builder, string setting)
{
return builder.Add(new MyLibraryCapability<T>(setting));
}
}
// Usage:
var bag = Composer.For(myObject)
.WithMyFeature("custom-setting") // Your extension method
.Build();
For Consumers
When using a library that accepts capability bags:
- Create capability bags for your objects:
var myObject = new MyClass();
var bag = Composer.For(myObject)
.Add(new RequiredCapability<MyClass>("value"))
.Add(new OptionalCapability<MyClass>(42))
.Build();
- Pass to library APIs:
library.Configure(bag);
๐งช Testing
The library includes comprehensive tests covering all scenarios:
- Comprehensive unit tests covering core functionality, edge cases, and performance
- 100% coverage on core types
- Thread safety tests for concurrent access
- Performance tests validating zero-allocation claims
Run tests:
dotnet test
๐ฆ Project Structure
Cocoar.Capabilities/
โโโ ICapability.cs # Core capability interfaces
โโโ ICapabilityBag.cs # Capability bag contract
โโโ CapabilityBag.cs # Implementation with Dictionary<Type, Array>
โโโ CapabilityBagBuilder.cs # Fluent builder with one-shot lifecycle
โโโ IOrderedCapability.cs # Ordering support
โโโ Composer.cs # Helper factory methods
โโโ Extensions/
โโโ CapabilityBagExtensions.cs # Convenience extension methods
๐ค Contributing
Contributions welcome! The library is designed to be:
- Stable: Core APIs are locked and won't change
- Extensible: New features can be added without breaking existing code
- Well-tested: All changes require comprehensive tests
๐ License
Apache-2.0 License - Use it anywhere, commercial or personal.
๐ฏ What's Next?
This library provides the foundation for capability-driven architectures. Build your own domain-specific wrapper APIs on top for the best developer experience!
Example integration: Check out how Cocoar.Configuration uses this library to provide type-safe configuration management with capabilities.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated |
---|---|---|
0.10.0 | 58 | 10/3/2025 |
0.7.0 | 114 | 9/30/2025 |
0.6.0 | 157 | 9/29/2025 |
0.5.0 | 157 | 9/29/2025 |
0.4.0 | 154 | 9/29/2025 |
0.4.0-alpha.0 | 113 | 9/28/2025 |
0.2.0-alpha.0 | 90 | 9/28/2025 |
See CHANGELOG.md for detailed release notes.