【问题标题】:Invoking a method via reflection with generics and overrides使用泛型和覆盖通过反射调用方法
【发布时间】:2011-06-14 21:31:20
【问题描述】:

我正在尝试调用 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() });

我的 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 示例放入代码中)

【问题讨论】:

    标签: c# generics reflection overriding


    【解决方案1】:

    我也不能通过nullMissing.Value,否则它会发出嘶嘶声。

    呱呱怎么办?您应该能够将null 传递给params 参数(当您通过M() 调用M(params object[] objects) 之类的方法时,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 用于将可选参数绑定到它们的默认值。

    【讨论】:

    • GetMethods 中的 LINQ 由于多次返回而失败(它有 16 个覆盖,其中一些具有不同的泛型类型参数),但是如果我使用我的方式获取方法信息,则调用RegisterType() 方法中的空引用异常仍然失败。由于 Container 不是 null,我只能假设 null 参数是原因。
    • 这很容易解决。在调用SingleOrDefault之前添加.Where(m =&gt; m.GetGenericArguments().Count() == 2)
    • 至于null遇到的问题,尝试通过静态方法调用来调用方法(即去掉反射)。只需致电Container.RegisterType&lt;IMyDataProvider, MockData.MockProvider&gt;("MockData", new ContainerControlledLifetimeManager(), null),看看您是否遇到同样的问题。如果这样做,问题不是动态调用,而是其他问题(可能是您设置容器的方式?)。
    • Container.RegisterType&lt;IMyDataProvider, MockData.MockProvider&gt;("MockData", new ContainerControlledLifetimeManager()) 工作正常 Container.RegisterType&lt;IMyDataProvider, MockData.MockProvider&gt;("MockData", new ContainerControlledLifetimeManager(), null) 抛出空引用异常。我怀疑它与容器的设置方式有什么关系。如果两者等同于运行时,那么我认为它们的行为应该相同。
    • 在上述 RegisterType 调用之前与容器相关的唯一代码是 UnityContainer Container = new UnityContainer()。所以我认为这排除了容器设置问题。
    【解决方案2】:

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

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

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

    感谢杰森的帮助。他的样本让我走上了最终找到答案的道路。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-27
      相关资源
      最近更新 更多