UnityContainer and internal constructor

2019-04-23 14:01发布

问题:

I have a class with internal constructor and want to Resolve it from Unity (2.0).

public class MyClass {
    internal MyClass(IService service) {
    }
}

then I'm doing

_container.Resolve<MyClass>();

when I do so I have an exception

Exception is: InvalidOperationException - The type MyClass cannot be constructed. 

IService is registered and the only problem is that constructor is internal. I really want this class to be public, but I want it to be creatable only via a factory (in which I'm actually calling container.Resolve<MyClass>()).

Is there a way to make Unity see that internal constructor? Like InternalsVisibleTo or something?

回答1:

I dug a little into how you might extend Unity for this purpose, and found some interesting information.

First, it seems that Unity selects which constructor to use by internally resolving an IConstructorSelectorPolicy. Included in Unity is the public abstract class ConstructorSelectorPolicyBase<TInjectionConstructorMarkerAttribute>, which includes this gem:

/// <summary>
/// Choose the constructor to call for the given type.
/// </summary>
/// <param name="context">Current build context</param>
/// <param name="resolverPolicyDestination">The <see cref='IPolicyList'/> to add any
/// generated resolver objects into.</param>
/// <returns>The chosen constructor.</returns>
public SelectedConstructor SelectConstructor(IBuilderContext context, IPolicyList resolverPolicyDestination)
{
    Type typeToConstruct = context.BuildKey.Type;
    ConstructorInfo ctor = FindInjectionConstructor(typeToConstruct) ?? FindLongestConstructor(typeToConstruct);
    if (ctor != null)
    {
        return CreateSelectedConstructor(context, resolverPolicyDestination, ctor);
    }
    return null;
}

FindInjectionConstructor and company are private static methods in this class which ultimately end up calling Type.GetConstructors (the overload with no parameters, which only returns public constructors). This tells me that if you can arrange for Unity to use your own constructor selector policy, which would be able to select any constructor, you are golden.

There is good documentation about how to make and utilize your own container extensions, so I imagine it's quite possible to make your own CustomConstructorSelectorPolicy that includes the relevant portions of DefaultUnityConstructorSelectorPolicy (which derives from the abstract base class and is the default unless you register something else) and ConstructorSelectorPolicyBase (deriving from this directly would probably not work well because key methods are not virtual, but you can reuse the code).

Therefore I 'd say it's doable with a moderate amount of hassle, but the end result would be quite "pure" from an engineering point of view.



回答2:

Unity will only look at public constructors, so you need to make this constructor public.

I really want this class to be public, but I want it to be creatable only via a factory

In that case, create a factory:

public class MyClassFactory : IMyClassFactory
{
    private readonly IService service;

    public MyClassFactory(IService service)
    {
        this.service = service;
    }

    MyClass IMyClassFactory.CreateNew()
    {
        return new MyClass(this.service);
    }
}

And register:

_container.Register<IMyClassFactory, MyClassFactory>();

And resolve:

_container.Resolve<IMyClassFactory>().CreateNew();

You can also use Unity's InjectionFactory:

container.Register<MyClass>(new InjectionFactory(c => 
{   
    return new MyClass(c.Resolve<IService>());
})); 

For this to work the assembly that holds this code should be able to see the internals of the assembly that holds the MyClass. In other words the MyClass assembly should be marked with InternalsVisibleTo.

What would also work is the following:

public static class MyClassFactory
{
    public static MyClass CreateNew(IService service)
    {
        return new MyClass(service);
    }
}

container.Register<MyClass>(new InjectionFactory(c => 
{   
    return MyClassFactory.Create(c.Resolve<IService>());
})); 

Although you won't have to make the constructor public, it is a great way to obfuscate your code :-)



回答3:

Just make the class internal and the constructor public...

  1. Interface public
  2. Class internal
  3. Constructor of class public.


回答4:

It's possible there are workarounds/hacks that would allow you to do this with Unity 9I don't know if any), but in general if you want a class to be managed by Unity (or any IOC container), it needs to be public with a public constructor.

One option might be to make an abstract factory that creates the class that has a public constructor, and keep the class's constructor internal. The downside is then your factory will be managed by Unity, but your class itself will not.