C# の List<T> は、通常は 同一型のオブジェクトを格納する ためのコレクションです。
そのため、T を interface で定義するのが最も素直で安全な方法だと思います。
しかし今回は、
-
既存のクラスが存在する
-
そのクラスに interface を追加できない
という前提を想定し、object 型を使って 複数のクラスのオブジェクトを混載する方法 を考えてみます。
基本方針
object 型を使う場合、元のクラスの型に キャストして使用する必要があります。
そのため、
-
値(object)
-
型識別用の情報
を セットで管理する必要があります。
また、型ごとにキャスト処理が異なるため、
取り出す際には switch 文による 多岐分岐 が必要になります。
サンプルコード
// 型識別用列挙型(Typeプロパティ用)
enum ItemType
{
ClassA,
ClassB,
}
// ClassA,ClassBはlistの要素のValueとなるクラス(既存のクラスを想定)
class ClassA
{
public int ValueA { get; set; }
}
class ClassB
{
public string ValueB { get; set; } = "";
}
// listの要素用のクラス。Valueを受け入れるコンテナ
class Item
{
public ItemType Type { get; } // 参照のみ
public object Value { get; } // 参照のみ
public Item(ItemType type, object value)
{
// 初期化で値をセット=> Value Objet => record化出来るかも。
Type = type;
Value = value;
}
}
// 使う側のコード
class Program
{
// エントリーポイント
static void Main()
{
var items = new List<Item>();
items.Add(new Item(
ItemType.ClassA,
new ClassA { ValueA = 10 }));
items.Add(new Item(
ItemType.ClassB,
new ClassB { ValueB = "hello" }));
// 数値の10と文字列のhelloがitemsに混載された。
// 以下取り出して使うコード
foreach (var item in items)
{
switch (item.Type)
{
case ItemType.ClassA:
{
if (item.Value is not ClassA a)
throw new InvalidOperationException(
$"Type=A but Value={item.Value.GetType()}"); // キャスト失敗は例外でアプリ終了
// ここから型安全
Console.WriteLine(a.ValueA);
break;
}
case ItemType.ClassB:
{
if (item.Value is not ClassB b)
throw new InvalidOperationException(
$"Type=B but Value={item.Value.GetType()}"); // キャスト失敗は例外でアプリ終了
Console.WriteLine(b.ValueB);
break;
}
default:
throw new NotSupportedException(
$"Unsupported Type: {item.Type}");
}
}
// 結果
// 10
// hello
}
}
感想
コードとしてはやや回りくどく、
そもそも 通常のアプリケーション設計で混載が必要になる場面は少ない ようにも感じます。
ただし、JSON や XAML といったデータフォーマットは、
このような「型の異なる値が混在する構造」を
さらに複雑に組み合わせて表現しています。
そう考えると、
-
JSON を言語として自然に扱える JavaScript の柔軟性の高さ
-
一方で、静的型付け言語で同じことをやろうとした場合の記述量の多さ
が、対照的に見えてきます。
今回のサンプルコードを見る限り、
C# でこの方法を多用するのは、あまり効率の良いやり方ではない
という印象も受けます。
ただし、
-
既存クラスを変更できない
-
境界データ(JSON / XAML / UI 要素など)を扱う
といった場面では、
現実的な選択肢のひとつになり得る方法だと思います。


コメント