Skip to content

Latest commit

 

History

History
172 lines (150 loc) · 4.36 KB

00.DelegateRegistration.md

File metadata and controls

172 lines (150 loc) · 4.36 KB

working with delegates

the beginnings

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;
            });
        }
    }
}

the second encounter

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>();