次の例を見てください(一部は 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のみを使用)はありますか?
これは確かに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
があなたの友達です。
IEnumerable<T>
のC#4で機能するため、次のことができます。
IEnumerable<Animal> animals = new List<Giraffe>();
ただし、List<T>
は共変射影ではないため、上記のようにリストを割り当てることはできません。
List<Animal> animals = new List<Giraffe>();
animals.Add(new Monkey());
これは明らかに有効ではありません。
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>
や他の同様のジェネリックインターフェイス(またはジェネリッククラスさえ)にバリアンスサポートが追加されなかったのは残念ですが、まあ、少なくとも何かあります。
他の人が述べたように、共分散/反変は、コンパイル時に型の安全性を保証することが不可能であるため、可変コレクションではサポートできません。ただし、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新しいコレクションを作成していることに注意してください。