Как использовать Activator для создания экземпляра универсального типа и приведения его обратно к этому типу?

У меня есть универсальный тип Store<T>, и я использую Activator для создания экземпляра этого типа. Теперь, как после использования Activator я могу преобразовать результирующий объект типа object обратно в созданный тип? Я знаю тип, который я использовал для создания экземпляра универсального. См. следующий код:

class Store<T> where T : IStorable 
{}

class Beer : IStorable 
{}

class BeerStore : Store<Beer>
{}

Type storeType = someObjectThatImplementsIStorable.GetType();
Type classType = typeof(Store<>);
Type[] typeParams = new Type[] { storeType };   
Type constructedType = classType.MakeGenericType(typeParams);

object x = Activator.CreateInstance(constructedType, new object[] { someParameter });

Я хотел бы сделать что-то вроде этого:

var store = (Store<typeof(objectThatImplementsIStorable)>)x;

но это не работает по понятным причинам. В качестве альтернативы я пробовал:

var store = (Store<IStorable>)x;

что, на мой взгляд, могло бы сработать, но дает InvalidCastException.

Как мне снова получить доступ к Store<T> методам, которые, как я знаю, находятся в объекте x?


person Bazzz    schedule 04.02.2012    source источник
comment
Можете ли вы сделать obvious причину более очевидной? Или просто скажите нам, в чем причина. Или вы действительно имеете в виду typeof..., не подставляя его на свой тип?   -  person Snowbear    schedule 04.02.2012
comment
@Snowbear этот фрагмент кода не компилируется, потому что Generics не позволяют использовать экземпляры типов в качестве T. Я не знаю этот тип, это может быть любой объект, реализующий интерфейс IStorable.   -  person Bazzz    schedule 04.02.2012
comment
Какой тип, по мнению отладчика, имеет x? Это правильный тип?   -  person CarneyCode    schedule 04.02.2012
comment
@Carnotaurus, да, отладчик считает правильный тип Store<someObjectThatImplementsIStorable>. Таким образом, активатор создает правильный объект, но упаковывает его в object, и теперь я хотел бы снова его распаковать.   -  person Bazzz    schedule 04.02.2012
comment
Коробка @Bazzz не является правильным термином, если общий тип хранилища не является структурой.   -  person phoog    schedule 05.02.2012
comment
@phoog, спасибо за исправление. Это не Struct, какой тогда правильный термин?   -  person Bazzz    schedule 05.02.2012
comment
@Bazzz К сожалению, я не могу придумать краткий термин для замены коробки. Ключевая концепция здесь заключается в том, что преобразование для ссылочных типов является ссылочным преобразованием, а преобразование упаковки или распаковки — нет. Я бы изменил формулировку на то, что активатор создает экземпляр правильного типа, но возвращает ссылку типа object, и теперь я хотел бы его понизить (или что-то в этом роде).   -  person phoog    schedule 05.02.2012


Ответы (5)


Поскольку фактический тип T доступен вам только через отражение, вам также потребуется получить доступ к методам Store<T> через отражение:

Type constructedType = classType.MakeGenericType(typeParams);

object x = Activator.CreateInstance(constructedType, new object[] { someParameter });
var method = constructedType.GetMethod("MyMethodTakingT");
var res = method.Invoke(x, new object[] {someObjectThatImplementsStorable});

EDIT Вы также можете определить дополнительный интерфейс IStore, который не использует дженерики, а вместо этого использует IStorable:

interface IStore {
    int CountItems(IStorable item);
}
class Store<T> : IStore where T : IStorable {
    int CountItems(IStorable item) {
        return count;
    }
}

Ваш Store<T> останется общим, но вы получите доступ к его CountItems, приведя к IStore:

var x = (IStore)Activator.CreateInstance(constructedType, new object[] { someParameter });
var count = x.CountItems((IStorable)someObjectThatImplementsStorable);
person Sergey Kalinichenko    schedule 04.02.2012
comment
Я боялся, что до этого дойдет. Является ли эмпирическим правилом, что, когда я знаю фактический тип T только через отражение, весь доступ к фактическому объекту также должен проходить через отражение? - person Bazzz; 04.02.2012
comment
@Bazzz Да, если единственное, что вы знаете о someObjectThatImplementsIStorable, это то, что это object, реализующий IStorable, вы мало что можете сделать, чтобы избежать размышлений. Однако если бы вы получали someObjectThatImplementsIStorable как общий тип T, вы могли бы также привести к Store<T>. В качестве альтернативы можно было бы избегать дженериков в подмножестве методов (я вскоре обновлю ответ, чтобы показать вам, как это сделать). - person Sergey Kalinichenko; 04.02.2012
comment
Я пойду этим путем, я знаю, что справлюсь с этой задачей, и другие варианты, похоже, недоступны. Спасибо за ответ. Я хотел бы увидеть подмножество, если у вас есть время. Просто из интереса и чтобы чему-то научиться. - person Bazzz; 04.02.2012
comment
@Bazzz Я только что обновил ответ. Я много раз использовал трюк с универсальным классом, реализующим обычный интерфейс, он очень мощный. - person Sergey Kalinichenko; 04.02.2012
comment
@dasblinkenlight, где он падает, находится везде, где вы потребляете или воздействуете на T, т.е. interface IStore { void Add(object item){}} != IStore‹T› {void Add(T item){}}, лично это в значительной степени только места, где я все равно использую дженерики - person Not loved; 04.02.2012
comment
@dasblinkenlight Действительно, я пробовал этот путь, но у меня это не сработало, потому что у моего Store есть метод public T GetSingleByID(int id), к которому я хотел бы получить доступ. Я никогда не смогу объявить этот универсальный метод в неуниверсальном интерфейсе, потому что интерфейс не будет знать о T. - person Bazzz; 06.02.2012
comment
@Bazzz Вы все еще можете попробовать это, добавив в свой интерфейс метод IStorable GetSingleStorableById(int it) и реализовав его, вызвав GetSingleByID. - person Sergey Kalinichenko; 06.02.2012

Ты не можешь просто завернуть его?

что-то типа

public Store<T> IConstructStore<T>(T item) where T : IStorable 
{
 return Activator.CreateInstance(typeof(Store<T>), new object[] { someParameter }) as Store<T>;
}

или я упускаю то, что вы пытаетесь сделать?

IE

class Program
{
    static void Main(string[] args)
    {
        Beer b = new Beer();
        var beerStore = IConstructStore(b);
        Console.WriteLine(beerStore.test);
        Console.WriteLine(beerStore.GetType().ToString());
    }

    public static Store<T> IConstructStore<T>(T item) where T : IStorable
    {
        return Activator.CreateInstance(typeof(Store<T>), new object[] { }) as Store<T>;
    }
}

interface IStorable { }

class Store<T> where T : IStorable
{
    public int test = 1;
}

class Beer : IStorable
{ }

отпечатки

1 
ConsoleApp1.Store'1[ConsoleApp1.Beer]
person Not loved    schedule 04.02.2012
comment
интересно, а потом? Как мне вызвать этот метод? Вы можете добавить строку примера кода? - person Bazzz; 04.02.2012
comment
да, пока вы вызываете его с любым объектом, T выводится, т.е. Beer item = new Beer(); var пиваStore = IConstructStore (предмет); пивоStore.whatever () должно работать нормально - person Not loved; 04.02.2012
comment
typeof(Store<T>) не компилируется, как и as Store<T> - person Bazzz; 04.02.2012
comment
Я использую Visual Studio 2010 и .Net 4. Ошибка компиляции: тип «T» не может использоваться в качестве параметра типа «T» в универсальном типе или методе «Store‹T›». Преобразование бокса или преобразование параметра типа из «T» в «IStorable» отсутствует. - person Bazzz; 04.02.2012
comment
Ах, теперь вы добавили раздел «Где». Может быть, это имело значение. Дай мне попробовать снова. - person Bazzz; 04.02.2012
comment
вы включили where T : IStorable в IConstructStore? - person Not loved; 04.02.2012
comment
Да, извините, кодирование с головы - person Not loved; 04.02.2012
comment
Нет, не повезло, активатор теперь делает Store<IStorable> вместо Store<objectThatImplementsIStorable>. Итак, теперь похоже, что он работает, но активатор создает неправильный объект, я ожидаю Store<Beer> или любой другой объект, который реализует IStorable, но ваш метод вместо него возвращает Store<IStorable>. - person Bazzz; 04.02.2012
comment
это не для меня, я добавил тестовый пример к моему ответу - person Not loved; 04.02.2012
comment
Я понял, в чем разница, в вашем случае вы передаете метод с Beer, в моем случае я передаю метод с IStorable, который на самом деле является Beer, но я не знаю, что в то время, когда я вызов метода. Итак, где у вас есть Beer beer = new Beer();, у меня есть IStorable objectThatImplementsIStorable;, мы оба используем это как параметр item. - person Bazzz; 04.02.2012
comment
Я нашел этот ответ очень полезным для моего случая. - person Charles Owen; 06.04.2021

Наиболее подходящим ответом, на мой взгляд, будет «вы не можете сделать это таким образом».

Вы можете попробовать ввести интерфейс IStorage и попытаться сделать его ковариантным или контравариантным (вы видели такой вариант?). Если это не вариант, например, если у вас есть входные и выходные универсальные типы, используемые в Storage, то нет возможности реализовать то, что вы хотите. Причина в том, что Storage<Beer> нельзя безопасно использовать как Storage<IStorable> из-за этого случая:

Storage<IStorable> store = new Storage<Beer>(); // let's pretend we can do it 
store.Save(new StorableButNotBeer()); // what will happen here?

Единственный возможный обходной путь для вас, как я вижу, - это отказаться от этого метода и привести объект в то место, где вы знаете все точные типы:

public void object CreateStore(Type istorableType)
{
    // here is your activator code, but you will have to return an object
}

var beerStore = (Store<Beer>)CreateStore(typeof(Beer));
person Snowbear    schedule 04.02.2012
comment
Спасибо за ваше предложение, у меня действительно есть входные и выходные универсальные типы в Store‹T›, так что этот путь не работает. Что касается другого предложения, кроме экземпляра objectThatImplementsIStorable, я нигде не знаю фактического типа. Я думаю, что в конечном итоге я буду использовать Reflection для объекта, созданного активатором. +1 за хорошее объяснение того, почему проблема существует. - person Bazzz; 04.02.2012

T должен быть типом Store, избегая использования typeof(Store

person Darío Andrés Muñoz Prudant    schedule 06.12.2013

Предположим, что someObjectThatImplementsIStorable имеет тип MyStorable.

например MyStorable someObjectThatImplementsIStorable = new MyStorable(); ... // остальная часть вашего кода здесь.

Тогда x нельзя привести к Store, но можно привести к Store. Следующее будет работать: (Магазин)x

Обратите внимание, что хотя MyStorable реализует IStorable, между Store и Store нет никакой связи. Это два разных класса, которые не являются производными друг от друга.

u.

person Uri    schedule 04.02.2012
comment
Вы не можете привести универсальный тип без включения T, т.е. (Store)x не будет компилироваться - person Not loved; 04.02.2012