Cysharp/MessagePipe

MessagePipe.IServiceCollection prevents using Microsoft.Extensions.DependencyInjection.IServiceCollection

amelkor opened this issue · 4 comments

If we have Microsoft.Extensions.DependencyInjection libs in Unity3D then Microsoft.Extensions.DependencyInjection.IServiceCollection conflicts with MessagePipe.IServiceCollection cause the latter replaces the first one.
Thus it's not possible to register MessagePipe within the original Microsoft.Extensions.DependencyInjection container.
The only workaround is to build MessagePipe with BuiltinContainerBuilder but nothing would be injected from the main container then.

This hinders usage of the Microsoft's implementation:

#if !UNITY_2018_3_OR_NEWER
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
#endif

Could that be optional?

A crutchy workaround that seems working though:

using System;
using System.Collections.Generic;
using System.Reflection;
using MessagePipe;
using Microsoft.Extensions.DependencyInjection;
using IServiceCollection = Microsoft.Extensions.DependencyInjection.IServiceCollection;

namespace Extensions.Cysharp
{
    public static class MessagePipeBuilderExtensions
    {
        public static IServiceCollection UseMessagePipe(this IServiceCollection services, Action<BuiltinContainerBuilder> configure = default)
        {
            var builder = new BuiltinContainerBuilder();
            builder.AddMessagePipe(options => options.InstanceLifetime = InstanceLifetime.Singleton);

            configure?.Invoke(builder);

            foreach (var (serviceType, implementationType) in (List<(Type serviceType, Type implementationType)>) _singleton.GetValue(builder))
            {
                services.AddSingleton(serviceType, implementationType);
            }
            
            foreach (var (serviceType, implementationType) in (List<(Type serviceType, Type implementationType)>) _transient.GetValue(builder))
            {
                services.AddTransient(serviceType, implementationType);
            }
            
            foreach (var singleton in (Dictionary<Type, object>) _singletonInstances.GetValue(builder))
            {
                services.AddSingleton(singleton.Key, singleton.Value);
            }

            return services;
        }

        private static readonly FieldInfo _singleton = typeof(BuiltinContainerBuilder).GetField("singleton", BindingFlags.Instance | BindingFlags.NonPublic);
        private static readonly FieldInfo _transient = typeof(BuiltinContainerBuilder).GetField("transient", BindingFlags.Instance | BindingFlags.NonPublic);
        private static readonly FieldInfo _singletonInstances = typeof(BuiltinContainerBuilder).GetField("singletonInstances", BindingFlags.Instance | BindingFlags.NonPublic);
    }
}

The only inconvenience now is the annoying ambiguency between IServiceCollection implementations which is hindrance for extensions methods like GetRequiredSevice<>

I see, I hadn't considered such cases at all.
I'll think about how to handle it.
Thank you.

This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 7 days.

well I see only one way, that is, using MessagePipe code and original IServiceCollection code, merging them on your own, and configure an automated alert if any of the two have vulnerability news popping up [alternately you could use a global usings-file which can be explicitly set to just one of the two | or you can also limit those global usings to an assembly or multiple assemblies connected using internals-visible-to and that way you could separate and run both versions at the same time, if that sounds promising ] Hmm thinking more about it, I came up with more ideas, you could also use MonoMod to patch the assembly by adding the code post-compiletime, compiling without what you actually want to have inside the assembly, but you add it later on - then you fool the compiler in a way, but don't suffer from any secondary negative sideeffects. Have a look at it here: https://github.com/MonoMod/MonoMod