everybody who worked at a legacy(?) codebase has seen this
public class MyClass {
public void DoWork() {
// stuff
MyService.Method();
// more stuff
}
}
we need to make a change MyClass
and no unit tests exists. MyService
is a
static class and unusable for unit testing.
fixing the issues with MyService
is way out of scope and we are eager to prove
our implementation behaves as expected.
how about the following approach
public class MyClass {
internal delegate void MyServiceMethod();
private MyServiceMethod _myServiceMethod;
internal MyClass(MyServiceMethod myServiceMethod) {
_myServiceMethod = myServiceMethod;
}
public void DoWork() {
// stuff
_myServiceMethod();
// more stuff
}
}
the test would look like
public class MyClassTest {
[Fact]
public void Test() {
var wasCalled = false;
var target = new MyClass(() => { wasCalled = true; });
target.DoWork();
wasCalled.Should().BeTrue();
}
}
and the delegate registration
public class MyClass {
internal class Module : Autofac.Module {
protected override void Load(ContainerBuilder builder) {
builder.Register<MyServiceMethod>(ctx => {
void MyServiceMethod() => MyService.Method();
return MyServiceMethod;
});
}
}
}
time goes by and somebody has slain the mighty MyService
so we simply use it.
public class MyClass {
internal class Module : Autofac.Module {
protected override void Load(ContainerBuilder builder) {
builder.Register<MyServiceMethod>(ctx => {
var myService = ctx.Resolve<MyService>();
void MyServiceMethod() => myService.Method();
return MyServiceMethod;
});
}
}
}
easy!
but what if we write it a bit different
public class MyClass {
internal class Module : Autofac.Module {
protected override void Load(ContainerBuilder builder) {
builder.Register<MyServiceMethod>(ctx => {
var myService = ctx.Resolve<MyService>();
return RegisterMyServiceMethod(service);
});
// or
//builder.Register<MyService, MyServiceMethod>(RegisterMyServiceMethod(service));
}
}
internal static MyServiceMethod RegisterMyServiceMethod(MyService myService) {
void MyServiceMethod() => myService.Method();
return MyServiceMethod;
}
}
in the module is just wiring.
i don't want to do this by hand!
i can haz Source Generator?
public partial class MyClass {
[RegisterDelegate]
internal static ServiceMethod RegisterMyServiceMethod(MyService) {
void MyServiceMethod() => myService.Method();
return MyServiceMethod;
}
//...
}
and we generate
partial class MyClass {
internal class Module : Autofac.Module {
protected override void Load(ContainerBuilder builder) {
builder.Register<MyClass.ServiceMethod>(ctx => {
var service = ctx.Resolve<IService>();
return RegisterMyServiceMethod(service);
});
}
}
}
wait you are telling me, *i still must write TWO registrations?!
builder.RegisterType<MyClass>();
builder.RegisterModule<MyClass.Module>();
no worries, we can fix this with net7 & C#11 static interface members and extension methods
public interface IRegisterADelegateModule {
static abstract Autofac.Core.IModule DelegateModule { get; }
}
public static class ContainerBuilderExtension {
public static IRegistrationBuilder<T, ConcreteReflectionActivatorData, SingleRegistrationStyle>
RegisterTypeWithDelegateModule<T>(this ContainerBuilder builder)
where T : IRegisterADelegateModule {
builder.RegisterModule(T.DelegateModule);
return builder.RegisterType<T>();
}
}
and we generate
partial class MyClass : IRegisterADelegateModule {
Autofac.Core.IModule IRegisterADelegateModule.DelegateModule { get; } = new Module();
//...
}
file class Module : Autofac.Module {
//...
}
so you can simply use
builder.RegisterTypeWithDelegateModule<MyClass>();