3

簡単に言うと、使用されるすべての型の親型を使用することで、配列内のさまざまな型パラメーターを使用してジェネリックスを格納できるようにしたいと思います。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>'

タイプの親クラスを使用してジェネリックスを配列に格納する方法はありますか、それともこれは単に不可能ですか?私はそれが可能であることを本当に望んでいます、さもなければそれは私のプログラムを悪夢にするでしょう...

編集:もう少しコンテキスト!

ゲームで敵を生成するクラスを作っています。私はそれらをテンプレートと呼んでいます(実際のテンプレートクラスとは関係ありません。ブループリントまたはファクトリーと呼んでもかまいません)。敵のコンストラクターはテンプレートを受け取り、それを使用して自身の値を決定します。ゲームが読み込まれると、テンプレートを使用して、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()
    { }
}

ゲームをロードするとき、私はそれらから敵をロードするために配列内のすべてのテンプレートを調べたいと思います。これは手動で実行できることはわかっていますが、新しいタイプの敵を作成するたびに、手動でコードに追加する必要があります(したがって、おそらく2回以上忘れてしまいます)。

私の2つのオプションは次のとおりです。1-ジェネリックを使用すると、上記の問題が発生します。2-非ジェネリックを使用し、型を内部に格納します。これにより、戻り型のGenerate()関数が固定されます。これは、生成関数がオブジェクトの配列を出力することを意味します。この配列は、テンプレートが敵の配列を生成するたびに適切なタイプに変換する必要があります。

私の頭の中には、これらすべてにエレガントな解決策があることを伝えるスペースがあります。それが正しいことを願っています。

4

3 に答える 3

7

はい、C#4は汎用バリアントをサポートしていますが、インターフェイスとデリゲートの宣言でのみサポートされているため、この場合はサポートできません。もちろん、インターフェースを作成できる可能性があります。

public interface IGeneric<out T>

次に、それをクラスに実装します。その時点で、を作成できますIGeneric<Animal>

あなたが達成しようとしていることについてより多くの詳細を与えることができれば、私たちはあなたが別のアプローチを見つけるのを助けることができるかもしれません。

于 2012-06-22T17:55:50.610 に答える
1

Jon Skeetの情報はさておき、次のようなことができるかもしれません。

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>()
于 2012-06-22T18:30:46.527 に答える
1

配列が複数のタイプのアイテムを保持する場合、アイテムは、配列自体とは別のヒープオブジェクトに格納する必要があります(一部のタイプが構造体の場合は、ボックス化する必要があります)。または、非ジェネリック型から派生したジェネリック型のフィールドとして格納されます)。ほとんどの場合、最も簡単な方法は、配列に格納するすべてのものに共通の祖先タイプを識別し、必要に応じて配列要素を型キャストすることです。ただし、それが実行できない場合がいくつかあります。たとえば、コレクションがタイプが不明であるが複数のインターフェイスに制約されているオブジェクトを保持する場合、メソッドタイプパラメータが同様に制約されているジェネリックルーチンにそれらのオブジェクトを渡す必要があります。

コレクション内のオブジェクトが少数のルーチンにのみ渡される場合は、アイテムを追加するジェネリックメソッドでデリゲートを構築して、必要なすべてのルーチンを適切に呼び出し、それらのデリゲートをコレクションの一部として格納できる可能性があります。これには、ラムダ式または匿名のデリゲートが便利な場合があります。

たとえば、リストに格納されているアイテムを、さまざまなオブジェクトのメソッドおよびWibble<T>さまざまなIWibblerオブジェクトのメソッドにフィードできる必要があるとします。ここで、タイプにはインターフェイス制約とがあります。Wobble<T>IWobblerTI1I2

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

    プライベート構造体WibbleWobbleDelegateSet
    {{
        public Action <IWibbler、int> Wibble;
        public Action <IWobbler、string> Wobble;
        static WibbleWobbleDelegateSet Create <T>(T param)ここで、T:I1、I2
        {{
            var ret = new WibbleWobbleDelegateSet();
            ret.Wibble =(IWibbler wibbler、int p2)=> {wibbler.Wibble <T>(param、p2); };
            ret.Wobble =(IWobbler wobbler、文字列p2)=> {wobbler.Wobble <T>(param、p2); };
            retを返します。
        }
    }

WibbleWobbleDelegateSet.Create<T>(T param)適切に制約されたでを呼び出すと、構造体の作成時に指定されたパラメーターを任意のメソッドまたはメソッドparamに渡すために使用できるデリゲートを含む非ジェネリック構造が生成されます。IWibbler.Wibble<T>()IWobbler.Wobble<T>()

このアプローチは、呼び出されるルーチンのリストがわかっている場合にのみ直接使用できます。制約されたジェネリックパラメーターを使用して任意のルーチンを呼び出すことができる必要がある場合は、トリッキーなインターフェイスまたはReflectionを使用して呼び出すことができますが、そのようなことはより複雑になります。

于 2012-06-22T19:58:17.447 に答える