通过泛型和覆盖反射调用方法

时间:2011-06-13 17:26:10

标签: c# generics reflection overrides

我正在尝试在Unity容器中调用RegisterType方法。 RegisterType总共有16个覆盖(其中一些是参数,有些是类型)。

我正在尝试执行相当于:

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

使用GetMethod()完全失败,所以我最终做了这个丑陋的事情:

     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() });

这很好用,直到Invoke因为我没有InjectionMember参数而抱怨(它们是可选的,我没有任何给予)。因此,根据文档,我必须使用Type.InvokeMember()来调用带有可选参数的方法。

所以我这样做了:

     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类执行此操作:

  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 });
  }

是的,这很丑陋,但我只是将它用于这个案例,所以它可以胜任。

现在,问题是,它仍然抱怨缺少第三个参数。我也不能传递null或Missing.Value,或者它会呱呱叫。我尝试过使用和不使用BindingFlags.OptionalParamBinding。我很难过。

(编辑将Container.RegisterType示例放在代码中)

2 个答案:

答案 0 :(得分:1)

  

我也无法通过nullMissing.Value,或者它会发出嘶嘶声。

克鲁克斯怎么样?您应该能够为null参数传递params(当您通过M(params object[] objects)调用类似M()的方法时,情况就会如此方法中objects为空。

其次,您可以更干净地查找方法。我没有触手可及的编译器,但试试这个:

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)
    });

然后调用这样的方法,为null参数传递params InjectionMember[]

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

很抱歉,如果它没有编译。如果它没有编译,这将使您非常接近正确的解决方案。

以下是一个有用的示例:

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 });
        }
    }
}

打印:

Hello, world!
17
True
DateTime

在控制台上。

  

我尝试过使用和不使用BindingFlags.OptionalParamBinding。我很难过。

params不是方法签名的一部分。允许可变长度参数列表是一种编译器技巧。 BindingFlags.OptionalParamBinding用于将可选参数绑定到其默认值。

答案 1 :(得分:1)

我的帖子提到我曾尝试将null作为第3个参数传递,并且应用程序“嘶哑”。具体来说,它获得了一个空引用异常,我应该更清楚。

解决方案是传递“new InjectionMember [0]”而不是null,因此Invoke()应该如下所示:

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

感谢Jason的帮助。他的样本让我走上了最终导致答案的道路。