C# supply-demand pattern implementation
$ dotnet add package SupplyDemandSupplyDemand is a tiny yet powerful C# library for dynamic, scoped, asynchronous dependency composition.
Add SupplyDemand to your project with NuGet:
dotnet add package SupplyDemand
Or with the Package Manager:
Install-Package SupplyDemand
using System;
using System.Threading.Tasks;
using SupplyDemand;
class Program
{
static async Task Main()
{
// 1. Define suppliers (async, strongly typed)
var getNumber = new Supplier<object, int>((data, scope) => Task.FromResult(10));
var getMessage = new Supplier<object, string>((data, scope) => Task.FromResult("Hello!"));
// The supplier registry must exist before sumSupplier can reference it in closure:
SupplierRegistry suppliers = null;
var sumSupplier = new Supplier<object, string>(async (data, scope) =>
{
int n = await scope.Demand<string, object, int>(new DemandProps<SupplierRegistry, string, object>
{
Type = "number",
Key = "number",
Suppliers = suppliers
});
string msg = await scope.Demand<string, object, string>(new DemandProps<SupplierRegistry, string, object>
{
Type = "message",
Key = "message",
Suppliers = suppliers
});
return $"{msg} Your number is {n}.";
});
suppliers = new SupplierRegistry
{
["number"] = getNumber,
["message"] = getMessage,
["sum"] = sumSupplier
};
var rootSupplier = new Supplier<object, string>((data, scope) =>
scope.Demand<string, object, string>(new DemandProps<SupplierRegistry, string, object>
{
Type = "sum",
Key = "sum",
Suppliers = suppliers
})
);
// 4. Run!
string result = await SupplyDemand.Init(rootSupplier, suppliers);
Console.WriteLine(result); // Output: Hello! Your number is 10.
}
}
Need to override/replace a supplier for just one demand?
Just "clone" your registry, replace the supplier, and pass the new registry for that call!
// Make sure your SupplierRegistry class contains a copy constructor:
// public SupplierRegistry(IDictionary<string, object> dict) : base(dict) { }
var numberSupplier = new Supplier<object, int>((data, scope) => Task.FromResult(123));
var suppliers = new SupplierRegistry { ["number"] = numberSupplier };
var rootSupplier = new Supplier<object, int>(async (data, scope) =>
{
// Clone and replace just for this demand:
var customSuppliers = new SupplierRegistry(suppliers)
{
["number"] = new Supplier<object, int>((d, s) => Task.FromResult(999))
};
return await scope.Demand<string, object, int>(new DemandProps<SupplierRegistry, string, object>
{
Type = "number",
Key = "number",
Suppliers = customSuppliers
});
});
int result = await SupplyDemand.Init(rootSupplier, suppliers);
Console.WriteLine(result); // Output: 999
Suppliers can call other suppliers dynamically and combine their results:
var suppliers = new SupplierRegistry();
suppliers["double"] = new Supplier<int, int>((data, scope) => Task.FromResult(data * 2));
suppliers["greet"] = new Supplier<string, string>((data, scope) => Task.FromResult("Hello, " + data));
suppliers["sumDoubles"] = new Supplier<(int, int), int>(async (data, scope) =>
{
int a = await scope.Demand<string, int, int>(new DemandProps<SupplierRegistry, string, int>
{
Type = "double",
Key = "doubleA",
Data = data.Item1,
Suppliers = suppliers
});
int b = await scope.Demand<string, int, int>(new DemandProps<SupplierRegistry, string, int>
{
Type = "double",
Key = "doubleB",
Data = data.Item2,
Suppliers = suppliers
});
return a + b;
});
var rootSupplier = new Supplier<(int, int), int>((data, scope) =>
scope.Demand<string, (int, int), int>(new DemandProps<SupplierRegistry, string, (int, int)>
{
Type = "sumDoubles",
Key = "sum",
Data = data,
Suppliers = suppliers
}));
int output = await SupplyDemand.Init(rootSupplier, suppliers, (10, 11));
// output == 42
Demand calls (with their own data and/or supplier registry).public class Supplier<TData, TReturn> : ISupplier<TData, SupplierRegistry, TReturn>
{
public Supplier(Func<TData, Scope<SupplierRegistry>, Task<TReturn>> func);
public Task<TReturn> Invoke(TData data, Scope<SupplierRegistry> scope);
}
SupplierRegistry is a Dictionary<string, object> that holds your named suppliers.public SupplierRegistry(IDictionary<string, object> dict) : base(dict) { }
Demand<TType, TData, TReturn>(DemandProps<SupplierRegistry, TType, TData> props):MIT