Найдите метод с помощью Reflection, создав список <T>правильного типа во время выполнения для аргумента CreateInstance. - PullRequest
2 голосов
/ 29 декабря 2010

У меня есть базовый класс (MyBase) с множеством возможных производных классов (в этом примере MyDerived и MyDerived2). По пути List<MyBase> передается другому классу, а затем этому классу необходимо вызвать конструктор еще одного класса ... который вызывается, полностью зависит от того, что в List.

Я могу использовать Reflection, чтобы найти конструктор, но только если где-то по пути я сделаю что-то вроде:

var r = baseList.ConvertAll(x => (MyDerived1)x);  

Так что r содержит List<MyDerived1>, и я могу найти конструктор с помощью Reflection. Конечно, это глупо, потому что я мог бы просто запечь конструктор, так как я пытаюсь запечь в MyDerived1 в этом ConvertAll утверждении. Но это один производный класс, и у меня есть десятки.

Я хочу, чтобы метод RunTest() вызывал правильный конструктор, полностью основываясь на том, что он находит в List<MyBase> во время выполнения. С практической точки зрения хорошо посмотреть на первый объект в списке, поскольку они всегда будут одного типа.

Возможно ли это? Как?

namespace Classtest
{
    class Program
    {
        static void Main(string[] args)
        {
            Test t = new Test( new List<MyBase>() {
                            new MyDerived1(),
                            new MyDerived1() } );
            t.RunTest();
        }
    }

    public class Test
    {
        List<MyBase> baseList;
        public Test(List<MyBase> bl)
        {
            baseList = bl;
        }
        public void RunTest()
        {
            // I would like the CreateInstance to figure out which constructor
            //    to call based on what it finds in baseList,
            //   without having to indicate "MyDerived1" here.
            // This works, but I'd rather have it figure this out at
            //   runtime for every single possible derived class...
            var r = baseList.ConvertAll(x => (MyDerived1)x);  
            Object o = Activator.CreateInstance(typeof(CallMe), 
                        new object[] { r });
        }
    }

    public class CallMe
    {
        public CallMe(List<MyDerived1> myDerived)
        {
            Console.WriteLine("Found it.");
        }
        public CallMe(List<MyDerived2> myDerived)
        {
            Console.WriteLine("Wrong one!");
        }
    }

    public class MyBase
    { }
    public class MyDerived1 : MyBase
    { }
    public class MyDerived2 : MyBase
    { }
}

Ответы [ 4 ]

2 голосов
/ 29 декабря 2010

Вы можете использовать MakeGenericType() для создания List<T> с правильным универсальным типом:

public void RunTest()
{
    // This method is creating a new list by doing the following:
    // var r = new List<baseList[0].GetType>(
    //             baseList.Cast<baseList[0].GetType()>);

    var elementType = baseList[0].GetType();

    // Get the System.Linq.Enumerable Cast<elementType> method.
    var castMethod = typeof(Enumerable)
                   .GetMethod("Cast", BindingFlags.Public | BindingFlags.Static)
                   .MakeGenericMethod(elementType);

    // Create a List<elementType>, using the Cast method to populate it.
    var listType = typeof(List<>).MakeGenericType(new [] { elementType });
    var r = Activator.CreateInstance(listType, 
            new [] {castMethod.Invoke(null, new [] {baseList})});

    Object o = Activator.CreateInstance(typeof(CallMe), 
               new [] { r });
}

Если ваши CallMe конструкторы могут быть изменены для получения IEnumerable<> параметр вместо List<>, тогда вы можете упростить RunTest(), удалив создание List<>:

public void RunTest()
{
    var elementType = baseList[0].GetType();

    // Get the System.Linq.Enumerable Cast<elementType> method.
    var castMethod = typeof(Enumerable)
                   .GetMethod("Cast", BindingFlags.Public | BindingFlags.Static)
                   .MakeGenericMethod(elementType);

    Object o = Activator.CreateInstance(typeof(CallMe), 
               new [] { castMethod.Invoke(null, new[] {baseList}) });
}
1 голос
/ 29 декабря 2010
public void RunTest()
{ 
    // it seems like you would want to run the following using reflection...
    // var myBaseList = this.baseList.OfType<this.baseList[0].GetType()>().ToList();

    Type[] genericTypeArray = new Type[] { this.baseList[0].GetType() };

    // call OfType to get IEnumerable<this.baseList[0].GetType()>
    MethodInfo ofTypeMethodInfo = typeof(Enumerable).GetMethods().Where(d => d.Name == "OfType").First();
    ofTypeMethodInfo = ofTypeMethodInfo.MakeGenericMethod(genericTypeArray);
    object myBaseEnumerable = ofTypeMethodInfo.Invoke(null, new object[] { this.baseList });

    // call ToList to get List<this.baseList[0].GetType()>
    MethodInfo toListMethodInfo = typeof(Enumerable).GetMethods().Where(d => d.Name == "ToList").First();
    toListMethodInfo = toListMethodInfo.MakeGenericMethod(genericTypeArray);
    object myBaseList = toListMethodInfo.Invoke(null, new object[] { myBaseEnumerable });

    Object o = Activator.CreateInstance(typeof(CallMe), new object[] { myBaseList }); 
}
0 голосов
/ 29 декабря 2010
public void RunTest()         
{            
 if (baseList.Count > 0)
 {
   Type ctorParam = typeof(List<>).MakeGenericType(baseList[0].GetType());
   object instance = typeof(CallMe).GetConstructor(new Type[] { ctorParam })
                            .Invoke(new object[] { baseList });   
 }
} 
0 голосов
/ 29 декабря 2010

Если CreateInstance не может найти правильный конструктор, попробуйте typeof (...). GetConstructor () и передайте правильные типы. Это даст вам правильный ctor, который вы можете использовать. Я не думаю, что есть другой, не отражающий способ, LINQ Cast <> и подобные методы требуют, чтобы тип был статически известен или задан как параметр типа, а это не тот случай, когда у вас есть список, который может содержать что угодно.

ОБНОВЛЕНИЕ: О, это не сработает, вы не можете передать List в конструктор List . Вот что вам нужно: вам нужно вызвать Cast <> с отражением. TypeOf (Enumerable) .GetMethod ( "Cast"). MakeGenericMethod (список [0] .GetType ()). Затем у вас есть список с правильным типом, поэтому нет необходимости в отражении конструктора, Activator будет работать с этим.

...