开发者

Generic type constructor resolution in C#, for IoC with generic service registrations

开发者 https://www.devze.com 2022-12-08 17:03 出处:网络
I\'m trying to add generic service support to our IoC container, and I have a question. Let\'s say I have the following type:

I'm trying to add generic service support to our IoC container, and I have a question.

Let's say I have the following type:

public interface IService<T> { ... }
public class Service<T> : IService<T> { ... }

and then the following code:

Type type = typeof(Service<>); // <-- notice no specific type here
ConstructorInfo ctor = LogicToFindCtor(type); // <-- picks one of the ctors

Now, having this, I have a generic type, and a constructor that I cannot call, since it was obtained through a generic type that has generic arguments present.

So, given that in my code I'm attempting to resolve a specific type, say IService<Employee>, I can easily find through my code that I need to resolve this using the concrete type Service<T>, and that I need to inject the type Employee in for T.

However, my question is this: Given that I have the ctor that I want to call already worked out on the generic non-specific type, how do I find the right constructor on the specific type.

Type specificType = typeof(Service<Employee>);
ConstructorInfo specificCtor = MapGenericToSpecific(ctor, specificType);

Edit: I noticed that the property MetaDataToken on both the constructors (the one from the non-specific type and the one from the specific type) has the same value here, can I use this to match? It would still involve a loop to find the right constructor, but one loop with a simple if-statement to find the right one is far preferrable to looking at arguments and attempting to match this way. I've changed the code below. This now compiles and runs, is there a better way with even less reflection, perhaps a direct lookup in the constructor table to find the constructor based on the metadatatoken value?

Here's the code:

using System;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
namespace ConsoleApplication10
{
    public class ActivationAttribute : Attribute { }

    public class TestClass<T1, T2>
    {
        public TestClass(String p1)
        {
            Console.Out.WriteLine("Wrong constructor");
        }

        [Activation]
        public TestClass(T1 p1)
        {
            Console.Out.WriteLine("Right constructor, p1=" + p1);
        }

        public TestClass(T2 p2)
        {
            Console.Out.WriteLine("Wrong constructor");
        }

        public TestClass()
        {
            Console.Out.WriteLine("Wrong constructor");
        }

        public TestClass(T1 p1, T2 p2)
        {
            Console.Out.WriteLine("Wrong constructor");
        }

        public TestClass(String p1, T2 p2)
        {
            Console.Out.WriteLine("Wrong constructor");
        }

        public TestClass(String p1, Int32 p2)
        {
            Console.Out.WriteLine("Wrong constructor");
        }
    }

    开发者_运维百科public class Program
    {
        static void Main(string[] args)
        {
            // This is the type I have in my IoC container
            Type genericType = typeof(TestClass<,>);

            // This is the constructor I locate
            ConstructorInfo genericCtor =
                (from ctor in genericType.GetConstructors()
                 where ctor.IsDefined(typeof(ActivationAttribute), false)
                 select ctor).First();
            Debug.Assert(genericCtor != null);

            // RESOLUTION STEP

            // Upon resolution, two actual types are specified
            Type[] genericArguments = new Type[] { typeof(String), typeof(Int32) };

            // So I create the actual type from the generic one
            Type specificType = genericType.MakeGenericType(genericArguments);

            // Can I look up the "genericCtor.MetadataToken" property directly?
            // or is this as good as it gets?
            ConstructorInfo specificCtor =
                (from ctor in specificType.GetConstructors()
                 where ctor.MetadataToken == genericCtor.MetadataToken
                 select ctor).First();
            Debug.Assert(specificCtor != null);

            Debug.Assert(specificCtor != null, "No matching constructors was found");
            Object instance = specificCtor.Invoke(new Object[] { "Test" });

            Console.Out.Write("Press enter to exit...");
            Console.In.ReadLine();
        }
    }
}


You can rely on the MetadataToken to remain the same on the bounded and unbounded constructor. The metadata is created with the IL code, it's part of the type and it's not related to the generic arguments binding.

.NET does not create different type for every use of generic argument (e.g. List<int> and List<bool>). It is the same type, but on every call the bounded generic arguments passed on stack with the method arguments (I know you may already know all this but I am just making sure).

Therefor, MetadataToken attached to the type doesn't change in runtime when the class generic arguments are bounded to to specific types. For example:

bool unboundAndBoundHasSameMetadataToken =
           typeof(IList<>).MetadataToken ==  typeof(IList<int>).MetadataToken;

bool boundedClassesHasSameMetadataToken =
           typeof(IList<bool>).MetadataToken == typeof(IList<int>).MetadataToken;

Assert.IsTrue(unboundAndBoundHasSameMetadataToken);
Assert.IsTrue(boundedClassesHasSameMetadataToken);

Bottom line, MetadataToken is "part of" the IL and doesn't differ on instances of same type with different generic arguments binding.


Out of curiosity (I am probably missing something), why not use the filter that finds the Activation constructor on the bounded type the same way it's found on the unbound type? The Activation attribute decorates the bounded constructor as well.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号