Collection types from Servus.Core — CircularQueue, TypeRegistry, LazyValueCache, and collection/async-enumerable extensions
Use when you need a bounded circular buffer, a type-keyed registry, a lazy-loading cache, or async enumerable helpers.
Type key (service locator, factory patterns)IMemoryCacheIAsyncEnumerable<bool> results with short-circuit evaluationNamespace: Servus.Core.Collections
public class CircularQueue<T>(int capacity)
{
void Enqueue(T item); // Overwrites oldest when full
bool TryDequeue(out T item);
int Count { get; }
int Capacity { get; }
IEnumerable<T> Items { get; }
}
public class TypeRegistry<TValue>
{
void Add<TKey>(TValue value);
void Add(Type key, TValue value); // AddOrUpdate semantics
TValue Get<TKey>(); // Throws KeyNotFoundException
TValue Get(Type key);
TValue GetOrAdd<TKey>(Func<TValue> factory);
TValue GetOrAdd(Type key, Func<TValue> factory);
}
Thread-safe — backed by ConcurrentDictionary.
public sealed class LazyValueCache<TKey, TValue> where TKey : notnull
{
LazyValueCache(IMemoryCache? cache = null, TimeSpan? defaultExpiration = null);
TimeSpan DefaultExpiration { get; set; } // Default: 30 minutes
TValue GetOrCreate(TKey type, Func<TValue> provider, TimeSpan? expiration = null);
bool TryGetValue(TKey type, out TValue? value);
}
Namespace: Servus.Core
public static class CollectionExtensions
{
void AddRange<T>(this ICollection<T> collection, IEnumerable<T> items);
bool IsEmpty<T>(this IReadOnlyCollection<T> enumerable);
}
public static class AsyncEnumerableExtensions
{
Task<bool> AnyAsync(this IAsyncEnumerable<bool> enumerable);
Task<bool> AnyAsync<T>(this IAsyncEnumerable<T> enumerable, Func<T, bool> predicate);
Task<bool> AllAsync(this IAsyncEnumerable<bool> enumerable);
Task<bool> AllAsync<T>(this IAsyncEnumerable<T> enumerable, Func<T, bool> predicate);
}
Both AnyAsync and AllAsync use short-circuit evaluation — they stop iterating as soon as the result is determined.
var recentLogs = new CircularQueue<LogEntry>(100);
// Always keeps the last 100 entries
recentLogs.Enqueue(new LogEntry("Request received"));
recentLogs.Enqueue(new LogEntry("Processing started"));
// Iterate over buffered items
foreach (var log in recentLogs.Items)
Console.WriteLine(log);
var serializers = new TypeRegistry<ISerializer>();
serializers.Add<OrderEvent>(new JsonSerializer());
serializers.Add<MetricEvent>(new ProtobufSerializer());
// Retrieve by type key
var serializer = serializers.Get<OrderEvent>();
serializer.Serialize(myEvent);
// Lazy creation
var s = serializers.GetOrAdd<PaymentEvent>(() => new JsonSerializer());
var cache = new LazyValueCache<string, UserProfile>(
defaultExpiration: TimeSpan.FromMinutes(5));
var profile = cache.GetOrCreate("user-42", () =>
{
// Only called on cache miss
return LoadUserProfile("user-42");
});
var registry = new ActionRegistry<IHealthCheck, HealthResult>();
bool allHealthy = await registry
.RunAllAsync(serviceProvider)
.AllAsync(result => result.IsHealthy);
Channel<T> instead.Get throws KeyNotFoundException. Use GetOrAdd if you want lazy creation.LazyValueCache delegates to IMemoryCache which is thread-safe, but the factory Func might not be — ensure the provider function is safe to call concurrently.