Передайте Instantiated System.Type в качестве параметра типа для универсального класса

183

Название немного неясно. Я хочу знать, возможно ли это:

string typeName = <read type name from somwhere>;
Type myType = Type.GetType(typeName);

MyGenericClass<myType> myGenericClass = new MyGenericClass<myType>();

Очевидно, MyGenericClass описывается как:

public class MyGenericClass<T>

В данный момент компилятор жалуется, что «Тип или пространство имен« myType »не может быть найден». Должен быть способ сделать это.

Роберт С. Барт
источник
Дженерики! = Шаблоны. Все переменные универсального типа разрешаются во время компиляции, а не во время выполнения. Это одна из тех ситуаций, когда «динамический» тип 4.0 может быть полезен.
1
@ Будет - как? При использовании с дженериками, в соответствии с текущим CTP, вы, по сути, в конечном итоге вызываете версии <object> (если я не пропускаю трюк ...)
Марк Грэвелл
@MarcGravell, который вы можете использовать foo.Method((dynamic)myGenericClass)для привязки методов во время выполнения, фактически шаблон поиска служб для перегрузок методов типа.
Крис Марисик,
@ChrisMarisic да, для какого-то общего public void Method<T>(T obj)- трюк, который я использовал более чем несколько раз за последние 6 лет с момента этого комментария; p
Марк Грэвелл
@MarcGravell, есть ли способ изменить это, чтобы метод создал его?
Барлоп

Ответы:

220

Вы не можете сделать это без отражения. Тем не менее, вы можете сделать это с отражением. Вот полный пример:

using System;
using System.Reflection;

public class Generic<T>
{
    public Generic()
    {
        Console.WriteLine("T={0}", typeof(T));
    }
}

class Test
{
    static void Main()
    {
        string typeName = "System.String";
        Type typeArgument = Type.GetType(typeName);

        Type genericClass = typeof(Generic<>);
        // MakeGenericType is badly named
        Type constructedClass = genericClass.MakeGenericType(typeArgument);

        object created = Activator.CreateInstance(constructedClass);
    }
}

Примечание: если ваш универсальный класс принимает несколько типов, вы должны включать запятые, когда опускаете имена типов, например:

Type genericClass = typeof(IReadOnlyDictionary<,>);
Type constructedClass = genericClass.MakeGenericType(typeArgument1, typeArgument2);
Джон Скит
источник
1
Хорошо, это хорошо, но как можно вызывать методы для созданного? Больше размышлений?
Роберт С. Барт
7
Что ж, если вы можете сделать так, чтобы ваш универсальный тип реализовывал неуниверсальный интерфейс, вы можете привести к этому интерфейсу. Кроме того , вы можете написать свой собственный универсальный метод , который делает все работы , которую вы хотите сделать с общим, и называют , что с отражением.
Джон Скит
1
Да, я не понимаю, как использовать созданный, если единственная информация о возвращенном типе у вас есть в переменной типа typeArgument? Мне кажется, что вы должны были бы привести к этой переменной, но вы не знаете, что это такое, поэтому я не уверен, что вы можете сделать это с помощью отражения. Другой вопрос, если объект имеет, например, тип int, если вы передадите его как переменную объекта, скажем, для примера List <int>, будет ли это работать? Будет ли созданная переменная рассматриваться как int?
theringostarrs
6
@ RobertC.Barth Вы также можете сделать «созданный» объект в примере типа «динамическим» вместо «объекта». Таким образом, вы можете вызывать методы для него, и оценка будет отложена до времени выполнения.
МакГарнагл
4
@balanza: Вы используете MakeGenericMethod.
Джон Скит
14

К сожалению, нет, нет. Универсальные аргументы должны быть разрешены во время компиляции как 1) допустимый тип или 2) другой универсальный параметр. Невозможно создать универсальные экземпляры, основанные на значениях времени выполнения, без большого труда использовать отражение.

JaredPar
источник
2

Некоторые дополнительные, как работать с кодом ножниц. Предположим, у вас есть класс, похожий на

public class Encoder() {
public void Markdown(IEnumerable<FooContent> contents) { do magic }
public void Markdown(IEnumerable<BarContent> contents) { do magic2 }
}

Предположим, во время выполнения у вас есть FooContent

Если бы вы смогли связать во время компиляции, вы бы хотели

var fooContents = new List<FooContent>(fooContent)
new Encoder().Markdown(fooContents)

Однако вы не можете сделать это во время выполнения. Чтобы сделать это во время выполнения, вы должны сделать следующее:

var listType = typeof(List<>).MakeGenericType(myType);
var dynamicList = Activator.CreateInstance(listType);
((IList)dynamicList).Add(fooContent);

Динамически вызывать Markdown(IEnumerable<FooContent> contents)

new Encoder().Markdown( (dynamic) dynamicList)

Обратите внимание на использование dynamicв вызове метода. Во время выполнения dynamicListбудет List<FooContent>(дополнительно также существующий IEnumerable<FooContent>), поскольку даже использование динамического все еще укоренено в строго типизированном языке, средство связывания времени выполнения выберет соответствующий Markdownметод. Если нет точных совпадений типов, он будет искать метод параметра объекта, и если ни один из них не совпадает, будет сгенерировано исключение связывателя времени выполнения, предупреждающее, что ни один метод не совпадает.

Очевидным недостатком этого подхода является огромная потеря безопасности типов во время компиляции. Тем не менее, код в этом направлении позволит вам работать в очень динамичном смысле, который во время выполнения все еще полностью напечатан, как вы ожидаете.

Крис Марисик
источник
2

Мои требования немного отличались, но, надеюсь, кому-то помогут. Мне нужно было прочитать тип из конфига и создать универсальный тип динамически.

namespace GenericTest
{
    public class Item
    {
    }
}

namespace GenericTest
{
    public class GenericClass<T>
    {
    }
}

Наконец, вот как вы это называете. Определите тип с помощью обратной черты .

var t = Type.GetType("GenericTest.GenericClass`1[[GenericTest.Item, GenericTest]], GenericTest");
var a = Activator.CreateInstance(t);
Мастер П
источник
0

Если вы знаете, какие типы будут переданы, вы можете сделать это без размышлений. Переключатель заявление будет работать. Очевидно, это будет работать только в ограниченном числе случаев, но это будет намного быстрее, чем отражение.

public class Type1 { }

public class Type2 { }

public class Generic<T> { }

public class Program
{
    public static void Main()
    {
        var typeName = nameof(Type1);

        switch (typeName)
        {
            case nameof(Type1):
                var type1 = new Generic<Type1>();
                // do something
                break;
            case nameof(Type2):
                var type2 = new Generic<Type2>();
                // do something
                break;
        }
    }
}
Тодд Скелтон
источник
это становится ужасно быстро, как только вы начинаете иметь дело с сотнями классов.
Майкл г
0

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

void AddValue<T>(object targetList, T valueToAdd)
{
    var addMethod = targetList.GetType().GetMethod("Add");
    addMethod.Invoke(targetList, new[] { valueToAdd } as object[]);
}

var listType = typeof(List<>).MakeGenericType(new[] { dynamicType }); // dynamicType is the type you want
var list = Activator.CreateInstance(listType);

AddValue(list, 5);

Точно так же вы можете вызвать любой другой метод в списке.

EGN
источник