Easy Delegation with C# Source Generators Library
In my last post I posted a wish list for C# that would make program construction easier. One of these was easy implementation of delegation, or in other words, Composition over Inheritance; that is the ability to easily do this:
interface IAccess
{
Task<Value> GetValue();
Task SetValue(Value value);
}
class NetworkAccess(HttpClient httpClient) : IAccess
{
public async Task<Value> GetValue()
{
var response = await httpClient.GetJson<Value>();
return response;
}
public async Task SetValue(Value value)
{
var response = await httpClient.PostJson(value);
}
}
class CachedAccess(ICache cache, IAccess innerAccess) : IAccess
{
public async Task<Value> GetValue()
{
var response = await innerAccess.GetValue();
await cache.SetValue(response);
return response;
}
public Task SetValue(Value value)
{
return inner.SetValue();
}
}
Well, after I got over the flu this Christmas, I began looking in anger at how to build source generators for C#, with the hope that I could make something like this reality. Using Decorator Generator as a base (which was very close to what I wanted - but I wanted less configuration), I created TypeAdoption, which auto-wires up inner implementations using an [Adopt] attribute. So taking the above example, you can do this:
interface IAccess
{
Task<Value> GetValue();
Task SetValue(Value value);
}
class NetworkAccess(HttpClient httpClient) : IAccess
{
public async Task<Value> GetValue()
{
var response = await httpClient.GetJson<Value>();
return response;
}
public async Task SetValue(Value value)
{
var response = await httpClient.PostJson(value);
}
}
// The class needs to be partial
partial class CachedAccess(ICache cache, IAccess innerAccess)
{
[Adopt]
private readonly IAccess _inner = innerAccess;
public async Task<Value> GetValue()
{
var response = await _inner.GetValue();
await cache.SetValue(response);
return response;
}
}
This will generate this implementation of IAccess:
partial class CachedAccess : IAccess
{
public Task SetValue(Value value)
{
return _inner.SetValue();
}
}
Note that GetValue has no auto-generated implementation, since it was implemented in the actual class. This can also be useful for choosing between multiple implementations. For example, say there is another interface that has SetValue with the same signature, and both are adopted, then you'll now have to determine how SetValue works:
interface ICache
{
public bool IsCached();
public Value Value { get; }
public Task SetValue(Value value);
}
partial class CachedAccess(ICache cache, IAccess innerAccess)
{
[Adopt]
private readonly ICache _cache = cache;
[Adopt]
private readonly IAccess _inner = innerAccess;
public async Task<Value> GetValue()
{
// ICache memebers now available as first-class members of the class.
if (IsCached())
return Value;
var response = await _inner.GetValue();
// Still just want to go directly to cache for this usage of SetValue()
await _cache.SetValue(key, response);
return response;
}
public async Task SetValue(Value value)
{
// Set both inner values - set the network access first, once that succeeds, cache the value.
await _inner.SetValue(value);
await _cache.SetValue(value);
}
}
Using these contrived examples, one can imagine how it would extend to interfaces with many members; perhaps you are using Refit to implement an extensive API, and a few methods need manual intervention: manually implement those methods and delegate the remaining members to the Refit implementation!
Again, the library is called TypeAdoption, and it's already available on NuGet.
Note posted on Wednesday, January 21, 2026 8:09 AM CST - link