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


182

Назва є своєрідною незрозумілою. Що я хочу знати, якщо це можливо:

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
@Will - яким чином? Якщо ви користуєтеся дженериками, під поточним CTP ви фактично закінчуєте виклик версій <object> (якщо я не пропускаю хитрість ...)
Marc Gravell

@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 type? Мені здається, що вам доведеться звернутися до цієї змінної, але ви не знаєте, що це таке. Я не впевнений, чи можете ви це зробити з відображенням. Ще одне питання, якщо об’єкт є, наприклад, типу int, якщо ви передаєте його як змінну об'єкта в скажімо, наприклад, список <int>, коли буде виконано цю роботу? чи буде розроблена змінна трактуватися як int?
theringostarrs

6
@ RobertC.Barth Ви також можете зробити "створений" об'єкт у прикладі типу "динамічний" замість "об'єкта". Таким чином ви можете викликати методи на ньому, і оцінка буде відкладена до виконання.
McGarnagle

4
@balanza: Ви використовуєте MakeGenericMethod.
Джон Скіт

14

На жаль, ні там немає. Загальні аргументи повинні бути вирішеними під час компіляції як 1) дійсний тип, так і 2) інший загальний параметр. Немає можливості створити загальні екземпляри на основі значень часу виконання без великого молотка використання відображення.


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>
    {
    }
}

Нарешті, ось як ви це називаєте. Визначте тип за допомогою backtick .

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

це стає некрасивим швидко, коли ви починаєте займатися з 100-х класів.
michael g

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

Аналогічно можна посилатися на будь-який інший метод у списку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.