Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invoking a method via reflection with generics and overrides

I'm trying to invoke the RegisterType method in the Unity container. RegisterType has a total of 16 overrides (some of those are parameters some are types).

I'm trying to perform the equivalent of:

Container.RegisterType<IMyDataProvider, MockData.MockProvider>("MockData", new ContainerControlledLifetimeManager())

Using GetMethod() was a total failure, so I ended up doing this ugly thing:

     MethodInfo registerTypeGeneric = Container.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).
        Where(p => p.ToString() == "Microsoft.Practices.Unity.IUnityContainer RegisterType[TFrom,TTo](System.String, Microsoft.Practices.Unity.LifetimeManager, Microsoft.Practices.Unity.InjectionMember[])").FirstOrDefault();
     MethodInfo registerTypeSpecific = registerTypeGeneric.MakeGenericMethod( new Type[] { typeof(IMyDataProvider), Assembly.LoadFrom("MockData.dll").GetType("MockData.MockProvider") });
     registerTypeSpecific.Invoke(Container, new object[] { "MockData", new ContainerControlledLifetimeManager() });

And this works beautifully, up until the Invoke which complains because I have no InjectionMember parameters (they're optional and I don't have any to give). So, according to the documentation, I have to use Type.InvokeMember() to call a method with optional parameters.

So I did this:

     Binder binder = new BootstrapperBinder();
     Container.GetType().InvokeMember("RegisterType",
        BindingFlags.Instance | BindingFlags.Public | BindingFlags.OptionalParamBinding | BindingFlags.InvokeMethod,
        binder,
        Container,
        new object[] { "MockData", new ContainerControlledLifetimeManager() });

My BoostrapperBinder class does this:

  public override MethodBase BindToMethod(BindingFlags bindingAttr, MethodBase[] match, ref object[] args, ParameterModifier[] modifiers, System.Globalization.CultureInfo culture, string[] names, out object state)
  {
     Type mockProvider = Assembly.LoadFrom("MockData.dll").GetType("MockData.MockProvider");
     state = new object();
     MethodInfo mi = Container.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).
        Where(p => p.ToString() == "Microsoft.Practices.Unity.IUnityContainer RegisterType[TFrom,TTo](System.String, Microsoft.Practices.Unity.LifetimeManager, Microsoft.Practices.Unity.InjectionMember[])").FirstOrDefault();
     return mi.MakeGenericMethod(new Type[] { typeof(ICarrierApprovalDataChangeAccessorEndPoint), mockProvider });
  }

Yes, it's ugly, but I just use it for this oen case, so it does the job.

Now, the problem is, it's still complaining about the lack of a third parameter. I can't pass null or Missing.Value either, or it croaks. I've tried with and without BindingFlags.OptionalParamBinding. I'm stumped.

(Edited to put the Container.RegisterType example in code)

like image 335
Pete Avatar asked Jun 13 '11 17:06

Pete


2 Answers

I can't pass null orMissing.Value either, or it croaks.

Croaks how? You should be able to pass null for a params parameter (when you invoke a method like M(params object[] objects) via M() it will be the case that objects is null within the method.

Second, you can lookup the method more cleanly. I don't have a compiler at my fingertips, but try this:

var registerTypeMethodInfo = 
     typeof(IUnityContainer).GetMethods()
                            .Where(m => m.Name == "RegisterType")
                            .Where(m => m.GetParameters()
                                 .Select(p => p.ParameterType)
                                 .SequenceEqual(new[] {
                                      typeof(string), 
                                      typeof(LifetimeManager),
                                      typeof(InjectionMember[])
                                 })
                            )
                            .Where(m => m.GetGenericArguments().Count() == 2)
                            .SingleOrDefault();
Assert.NotNull(registerTypeMethodInfo);
var methodInfo = 
    registerTypeMethodInfo.MakeGenericMethod(new[] {
        typeof(IMyDataProvider), 
        typeof(MockData.MockProvider)
    });

Then invoke the method like so, passing null for the params InjectionMember[] parameter:

methodInfo.Invoke(
    Container,
    new object[] { 
        "MockData",
        new ContainerControlledLifetimeManager(),
        null
    }
);

Sorry if it doesn't compile. If it doesn't compile, this will get you very close to a correct solution.

Here is a self-contained example that works:

namespace ParamsTest {
    interface Foo {
        void M<T>(string s, int n, params object[] objects);
    }
    class Bar : Foo {
        public void M<T>(string s, int n, params object[] objects) {
            Console.WriteLine(s);
            Console.WriteLine(n);
            Console.WriteLine(objects == null);
            Console.WriteLine(typeof(T).Name);
        }
    }
    internal class Program {
        internal static void Main(string[] args) {
            var genericMethodInfo =
                typeof(Foo).GetMethods()
                    .Where(m => m.Name == "M")
                    .Where(m => m.GetParameters()
                       .Select(p => p.ParameterType)
                       .SequenceEqual(new[] {
                           typeof(string),
                           typeof(int),
                           typeof(object[])
                       })
                    )
                    .Where(m => m.GetGenericArguments().Count() == 1)
                    .SingleOrDefault();
            var methodInfo =
                genericMethodInfo.MakeGenericMethod(
                    new[] { typeof(DateTime) }
                );
            var bar = new Bar();
            methodInfo.Invoke(bar, new object[] { "Hello, world!", 17, null });
        }
    }
}

This prints:

Hello, world!
17
True
DateTime

on the console.

I've tried with and without BindingFlags.OptionalParamBinding. I'm stumped.

params is not part of the signature of a method. It is a compiler trick to allow variable-length parameter lists. BindingFlags.OptionalParamBinding is for binding optional parameters to their default values.

like image 109
jason Avatar answered Oct 23 '22 19:10

jason


My initial post mentioned that I had tried passing null as a 3rd parameter and that the app "croaked." Specifically, it was getting a null reference exception and I should have been more clear about that.

The solution was to pass "new InjectionMember[0]" instead of null, so the Invoke() should have looked like this:

registerTypeSpecific.Invoke(Container, new object[] { "MockData", new ContainerControlledLifetimeManager(), new InjectionMember[0] }); 

Thanks to Jason for his help. His sample sent me down the path that eventually led to the answer.

like image 35
Pete Avatar answered Oct 23 '22 18:10

Pete