2012-06-22 4 views
2

Короче говоря, я хотел бы иметь возможность хранить генерики с использованием параметров другого типа в массиве, используя родительский тип для всех используемых типов. MSDN упомянул, что это невозможно, поскольку дженерики были инвариантными типами, но в комментарии говорилось, что это изменилось со времен 4.0.Массив из разных генераторов

Вот простой пример того, что я хотел бы сделать:

public class Animal 
    { 
    } 
    public class Dog : Animal 
    { 
    } 
    public class Cat : Animal 
    { 
    } 

    public class MyGeneric<T> 
    { } 
    public class MyInheritedGeneric<T> : MyGeneric<T> 
    { } 

    static void Main(string[] args) 
    { 
     MyGeneric<Animal>[] myGenericArray = new MyGeneric<Animal>[] 
     { 
      new MyGeneric<Dog>(), 
      new MyInheritedGeneric<Cat>() 
     }; 
    } 

Это возвращает аналогичные ошибки:

Cannot implicitly convert type 
'InheritanceTest.Program.MyGeneric<InheritanceTest.Program.Dog>' to 
'InheritanceTest.Program.MyGeneric<InheritanceTest.Program.Animal>' 

Cannot implicitly convert type 
'InheritanceTest.Program.MyInheritedGeneric<InheritanceTest.Program.Cat>' 
to 'InheritanceTest.Program.MyGeneric<InheritanceTest.Program.Animal>' 

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

EDIT: немного больше контекста!

Я делаю классы для создания врагов в игре. Я называю их «Шаблоны» (ничего общего с фактическими классами шаблонов, я вполне мог бы назвать их «Чертежи или фабрики»). Конструктор-враг использует шаблон, который он использует для определения своих собственных значений. Когда игра загружается, шаблоны используются для генерации всех врагов, используя их функцию Generate(), которая возвращает массив соответствующего типа, который они назначают для создания. Все объекты, которые должны быть созданы с помощью шаблона, должны иметь конструктор, принимающий шаблон в качестве единственного параметра.

public class Template<T> 
{ 
    protected static Random random = new Random(); 
    protected int _amount; 

    public int Amount 
    { 
     get { return _amount; } 
    } 

    public virtual T CreateInstance() 
    { 
     return (T)Activator.CreateInstance(typeof(T), this); 
    } 
    public virtual T[] Generate() 
    { 
     T[] objects = new T[Amount]; 
     for (int i = 0; i < Amount; ++i) 
      objects[i] = CreateInstance(); 
     return objects; 
    } 
} 

Реферат файла BasicZombie.cs, который содержит фактический класс противника и шаблон.

class Tpl_BasicZombie : Tpl_Enemy<BasicZombie> 
{ 
    public Tpl_BasicZombie() 
    { 
     _hp = 4; 
     _speed = 3; 
     _amount = 10; 
    } 
} 

class BasicZombie : GroundEnemy 
{ 
    public BasicZombie(Tpl_BasicZombie template) 
     : base(template, TextureManager.Get("zombie_base"), 1, 8) 
    { } 

    public void StuffHappens() 
    { } 
} 

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

Мои два варианта: 1- Использовать общий, и вышеуказанная проблема возникает. 2- Используйте не общий и сохраните тип внутри, который будет привязывать функцию Generate() возвращаемого типа. Это означало бы, что функция generate выведет массив объектов, массив, который нужно будет преобразовать в подходящий тип каждый раз, когда шаблон генерирует массив врагов.

У меня есть место в голове, которое говорит мне, что есть элегантное решение для всего этого, и я надеюсь, что это правильно!

+1

Вы уже читали [Ковариационный и контравариантный FAQ] (http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx?Redirected=true)? – 48klocs

+0

У меня нет. Я посмотрел, и это было очень полезно! Хотел бы я знать эти ключевые слова раньше. Я думаю, что вполне могу использовать интерфейс для ковариации. –

ответ

1

информация Джона Скита в сторону, вы могли бы быть в состоянии сделать что-то вроде этого:

public MyGeneric<T2> ToOtherType<T2>() 
{ 
    if (typeof(T2).IsAssignableFrom(typeof(T))) 
    { 
     // todo: get the object 
     return null; 
    } 
    else 
     throw new ArgumentException(); 
} 

     new MyGeneric<Dog>().ToOtherType<Animal>(), 
     new MyInheritedGeneric<Cat>().ToOtherType<Animal>() 
6

Да, C# 4 поддерживает generic variants - но только в объявлениях интерфейсов и делегатов, поэтому вы не сможете это сделать в этом случае. Конечно, вы могли бы потенциально создать интерфейс:

public interface IGeneric<out T> 

, а затем реализовать, что в классах, в какой момент вы можете создать IGeneric<Animal>.

Если вы можете дать более подробную информацию о том, чего вы пытаетесь достичь, мы сможем помочь вам найти альтернативный подход.

+0

Спасибо за ваш ответ! Я буду смотреть в него. Я также добавил подробное описание моей проблемы выше. Другой вопрос: каково значение ключевого слова out в контексте общего интерфейса? –

+0

@Cristophe: В этом случае 'out' означает, что тип' T' может отображаться только как «выход» элементов интерфейса (т. Е. Как свойство только для чтения или возвращаемое значение метода). – Gabe

+0

Благодарим за помощь. Я использовал интерфейс с ключевым словом out для ковариации, однако он имел неприятный эффект на программу ... Он разбивает его, когда файлы игры построены. Когда я работаю над XNA для Windows-телефона, я немного поработал, и я прочитал, что инфраструктура XNA поддерживает только полную функциональность .NET 4.0 для игр в Windows ... О, ну, по крайней мере, я узнал несколько вещей :) –

1

Если массив будет иметь более одного типа элемента, элементы собираются должны храниться в кучи объектов, которые отделены от самого массива (если некоторые из типов являются структурами, они должны быть либо помещены в бокс, либо сохранены как поле в родовом типе, которое происходит из не общего). В большинстве случаев проще всего определить общий тип предка для всего, что вы будете хранить в массиве, и просто придумать элементы массива по мере необходимости. Однако есть несколько случаев, когда это будет невозможно. Если, например, ваша коллекция будет содержать объекты, тип которых неизвестен, но ограничен более чем одним интерфейсом, необходимо будет передать эти объекты в общие подпрограммы, параметры метода которых аналогичным образом ограничены, и типы, которые могут быть переданные вашей рутине, не имеют общего предка, который удовлетворяет всем ограничениям, не будет ни одного типа, к которому могут быть применены все члены вашей коллекции, которые позволяли бы их передавать в качестве подходящего общего.

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

Например, предположу, что один нужно будет иметь возможность кормить предметы, которые хранятся в списке в Wibble<T> метод различных IWibbler объектов и Wobble<T> метода различных IWobbler объектов, где T типов имеют ограничение интерфейса I1 и I2 ,

 
    interface IWibbler { void Wibble<T>(T param, int param) where T : I1,I2; } 
    interface IWobbler { void Wobble<T>(T param, string param) where T: I1,I2; } 

    private struct WibbleWobbleDelegateSet 
    { 
     public Action<IWibbler, int> Wibble; 
     public Action<IWobbler, string> Wobble; 
     static WibbleWobbleDelegateSet Create<T>(T param) where T: I1, I2 
     { 
      var ret = new WibbleWobbleDelegateSet(); 
      ret.Wibble = (IWibbler wibbler, int p2) => { wibbler.Wibble<T>(param, p2); }; 
      ret.Wobble = (IWobbler wobbler, string p2) => { wobbler.Wobble<T>(param, p2); }; 
      return ret; 
     } 
    } 

Вызов WibbleWobbleDelegateSet.Create<T>(T param), с соответствующим образом ограниченного param, даст неуниверсальную структуру, которая содержит делегат, которые могут быть использованы для передачи параметра, поставляемый при создании структуры к любому IWibbler.Wibble<T>() или IWobbler.Wobble<T>() методу.

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

Смежные вопросы