web-dev-qa-db-ja.com

C#の分散の問題:List <Derived>をList <Base>として割り当て

次の例を見てください(一部は MSDN Blog から抜粋):

class Animal { }
class Giraffe : Animal { }

static void Main(string[] args)
{
    // Array assignment works, but...
    Animal[] animals = new Giraffe[10]; 

    // implicit...
    List<Animal> animalsList = new List<Giraffe>();

    // ...and explicit casting fails
    List<Animal> animalsList2 = (List<Animal>) new List<Giraffe>();
}

これは共分散問題ですか?これは将来のC#リリースでサポートされ、賢い回避策(.NET 2.0のみを使用)はありますか?

55
AndiDog

これは確かにC#4ではサポートされません。根本的な問題があります。

List<Giraffe> giraffes = new List<Giraffe>();
giraffes.Add(new Giraffe());
List<Animal> animals = giraffes;
animals.Add(new Lion()); // Aargh!

キリンを安全に保ちましょう:安全でない差異に対してはノーと言ってください。

配列バージョンが機能するのは、配列doが実行時間チェックを使用して参照型の差異をサポートするためです。ジェネリックスの要点はcompile-time型安全性を提供することです。

C#4ではsafeジェネリックバリアンスがサポートされますが、インターフェイスとデリゲートのみがサポートされます。だからあなたはできるようになります:

Func<string> stringFactory = () => "always return this string";
Func<object> objectFactory = stringFactory; // Safe, allowed in C# 4

Tは出力位置でのみ使用されるため、Func<out T>covariant in Tです。 Tは入力位置でのみ使用されるため、Tで反変であるAction<in T>と比較して、これを安全にしてください。

Action<object> objectAction = x => Console.WriteLine(x.GetHashCode());
Action<string> stringAction = objectAction; // Safe, allowed in C# 4

IEnumerable<out T>も共変であり、他の人が指摘するように、これをC#4で正しくします。

IEnumerable<Animal> animals = new List<Giraffe>();
// Can't add a Lion to animals, as `IEnumerable<out T>` is a read-only interface.

C#2の状況でこれに対処するという観点から、oneリストを維持する必要がありますか、それとも新しいリストを作成してよろしいですか?それが許容できる場合は、List<T>.ConvertAllがあなたの友達です。

111
Jon Skeet

IEnumerable<T>のC#4で機能するため、次のことができます。

IEnumerable<Animal> animals = new List<Giraffe>();

ただし、List<T>は共変射影ではないため、上記のようにリストを割り当てることはできません。

List<Animal> animals = new List<Giraffe>();
animals.Add(new Monkey());

これは明らかに有効ではありません。

16
Lee

List<T>に関しては、あなたは運が悪いと思います。ただし、.NET 4.0/C#4.0では、共変/反変インターフェースのサポートが追加されています。具体的には、IEnumerable<T>IEnumerable<out T> として定義されるようになりました。これは、型パラメーターがcovariant

これは、C#4.0でこのようなことができることを意味します...

// implicit casting
IEnumerable<Animal> animalsList = new List<Giraffe>();

// explicit casting
IEnumerable<Animal> animalsList2 = (IEnumerable<Animal>) new List<Giraffe>();

注:配列型も共変です(少なくとも.NET 1.1以降)。

IList<T>や他の同様のジェネリックインターフェイス(またはジェネリッククラスさえ)にバリアンスサポートが追加されなかったのは残念ですが、まあ、少なくとも何かあります。

9
Noldorin

他の人が述べたように、共分散/反変は、コンパイル時に型の安全性を保証することが不可能であるため、可変コレクションではサポートできません。ただし、C#3.5で一方向の変換をすばやく実行することは可能です。

List<Giraffe> giraffes = new List<Giraffe>();
List<Animal> animals = giraffes.Cast<Animal>().ToList();

もちろん、同じことではなく、実際には共分散ではありません。実際に別のリストを作成していますが、いわば「回避策」です。

.NET 2.0では、配列の共分散を利用してコードを簡略化できます。

List<Giraffe> giraffes = new List<Giraffe>();
List<Animal> animals = new List<Animal>(giraffes.ToArray());

ただし、ここでは実際にtwo新しいコレクションを作成していることに注意してください。

5
Aaronaught