Вызов метода через отражение с помощью обобщений и переопределений - PullRequest
6 голосов
/ 13 июня 2011

Я пытаюсь вызвать метод RegisterType в контейнере Unity.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 в код)

Ответы [ 2 ]

1 голос
/ 14 июня 2011

В моем первоначальном посте упоминалось, что я пытался передать ноль в качестве третьего параметра и что приложение "каркало".В частности, он получал исключение нулевой ссылки, и я должен был быть более ясным по этому поводу.

Решением было передать «новый InjectionMember [0]» вместо нуля, поэтому Invoke () должен был выглядеть следующим образомэто:

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

Спасибо Джейсону за помощь.Его образец отправил меня по пути, который в итоге привел к ответу.

1 голос
/ 13 июня 2011

Я тоже не могу пройти null или Missing.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 - для привязкинеобязательные параметры к их значениям по умолчанию.

...