Invoking a method via reflection with generics and

2019-02-22 03:44发布

问题:

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)

回答1:

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.



回答2:

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.