A lightweight, strongly-typed property bag library for .NET that provides type-safe key-value storage with support for scoped values, cloning, and dependency injection.
- Strongly-Typed Keys - Type-safe
PropertyBagKey<T>ensures compile-time type checking when storing and retrieving values - Nullable Value Support - Explicitly store and retrieve
nullvalues for reference types and nullable value types - Read-Only Interface -
IReadOnlyPropertyBagfor passing property bags to code that should only read values - Fluent API - Method chaining support for
SetandRemoveoperations - Scoped Values - Temporarily override values with automatic cleanup using
IPropertyBagScope - Cloning Support - Create independent copies with
Clone(), supporting deep cloning forICloneablevalues - Lazy Initialization - Empty property bags are lightweight with no memory allocated until first use
- Dictionary Access -
DefaultPropertyBagimplementsIReadOnlyDictionary<PropertyBagKey, object?>for enumeration - Dependency Injection - Built-in integration with
Microsoft.Extensions.DependencyInjection - Convenience Extensions - Automatic key name inference using
CallerArgumentExpression
dotnet add package NCode.PropertyBagOr for abstractions only:
dotnet add package NCode.PropertyBag.Abstractions// Define keys (typically as static readonly fields)
public static readonly PropertyBagKey<string> UserNameKey = new("UserName");
public static readonly PropertyBagKey<int> UserAgeKey = new("UserAge");
// Create and use a property bag
var propertyBag = PropertyBagFactory.Create();
propertyBag
.Set(UserNameKey, "Alice")
.Set(UserAgeKey, 30);
if (propertyBag.TryGetValue(UserNameKey, out var userName))
{
Console.WriteLine($"User: {userName}"); // Output: User: Alice
}Temporarily override a value that automatically restores when disposed:
var cultureKey = new PropertyBagKey<string>("Culture");
propertyBag.Set(cultureKey, "en-US");
using (propertyBag.Scope(cultureKey, "fr-FR"))
{
// Within this scope, Culture is "fr-FR"
propertyBag.TryGetValue(cultureKey, out var culture); // "fr-FR"
}
// After disposal, Culture is restored to "en-US"
propertyBag.TryGetValue(cultureKey, out var restored); // "en-US"Register services with the DI container:
services.AddPropertyBag();Then inject IPropertyBagFactory where needed:
public class MyService
{
private readonly IPropertyBagFactory _factory;
public MyService(IPropertyBagFactory factory)
{
_factory = factory;
}
public IPropertyBag CreateContext() => _factory.Create();
}Use automatic key name inference:
var connectionString = "Server=localhost;Database=test";
var timeout = TimeSpan.FromSeconds(30);
// Keys are inferred as "connectionString" and "timeout"
propertyBag
.Set(connectionString)
.Set(timeout);
// Retrieve with inferred key name
// Note: declare variable first - inline declaration (out string? x) won't infer the key name correctly
string? connectionString;
if (propertyBag.TryGet(out connectionString)) // Key inferred as "connectionString"
{
Console.WriteLine(connectionString);
}Null values can be explicitly stored and retrieved:
var optionalNameKey = new PropertyBagKey<string?>("OptionalName");
// Explicitly store null
propertyBag.Set(optionalNameKey, null);
// TryGetValue returns true because the key exists (even though value is null)
if (propertyBag.TryGetValue(optionalNameKey, out var name))
{
// name is null here, but the key was found
Console.WriteLine(name ?? "(not set)");
}
// This is different from a missing key
var missingKey = new PropertyBagKey<string?>("Missing");
if (!propertyBag.TryGetValue(missingKey, out _))
{
// Key does not exist
}| Type | Description |
|---|---|
PropertyBagKey |
Non-generic key with Type and Name properties |
PropertyBagKey<T> |
Strongly-typed key for type-safe value access |
IReadOnlyPropertyBag |
Read-only interface with TryGetValue and Clone |
IPropertyBag |
Mutable interface adding Set, Remove, and Scope |
IPropertyBagScope |
Disposable scope for temporary value overrides |
IPropertyBagFactory |
Factory interface for creating property bags |
PropertyBagExtensions |
Convenience methods with automatic key inference |
| Type | Description |
|---|---|
DefaultPropertyBag |
Dictionary-based implementation with lazy initialization |
DefaultPropertyBagFactory |
Default factory with singleton support |
DefaultPropertyBagScope |
Scope implementation that removes values on dispose |
PropertyBagFactory |
Static convenience class for creating property bags |
DefaultRegistration |
DI registration extension methods |
Licensed under the Apache License, Version 2.0. See LICENSE.txt for details.
- .NET 8.0
- .NET 10.0
- v1.0.0 - Initial release
- v1.0.1 - Fix CI build
- v1.1.0 - Added NET 8.0 build
- v1.1.1 - Fixed TryGet documentation example to show correct variable declaration pattern
- v1.2.0 - Added explicit support for nullable values;
TryGetValuenow returnstruewhen a key exists with anullvalue