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 开发者_StackOverflowlack 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)
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.
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.
精彩评论