異なる型が混在するコレクションの処理
C#は静的型付け言語ですが、object[](オブジェクト配列)や、古いArrayList、あるいは基底クラスのリストなど、異なるデータ型の要素が混在しているコレクションを扱う場面が存在します。
そのようなコレクションから、「文字列(string)だけを取り出したい」や「整数(int)だけを合計したい」といった処理を行う際、foreachループの中でis演算子を使って型チェックを行い、キャストするのは記述が冗長になります。
LINQの**OfType<T>**メソッドを使用すると、指定した型に適合する要素だけをフィルタリングし、同時にキャストまで完了したシーケンスを取得できます。
この記事では、OfType<T>の基本的な使い方と、Cast<T>メソッドとの重要な違いについて解説します。
OfType<T> メソッドの基本
OfType<T>メソッドは、コレクションの全要素を走査し、指定した型Tにキャスト可能な要素のみを抽出して新しいシーケンスを返します。型が合わない要素は、エラーにならずに単に**無視(スキップ)**されます。
コード例1:object配列から特定の型を抽出
数値、文字列、日付などが混在するobject型の配列から、string型の要素だけを抜き出す例です。
using System;
using System.Collections.Generic;
using System.Linq; // LINQを使用するために必須
public class OfTypeBasicExample
{
public static void Main()
{
// さまざまな型が混在するデータソース
object[] mixedData = new object[]
{
"SystemLog", // string
404, // int
DateTime.Now, // DateTime
"UserError", // string
500, // int
12.5m // decimal
};
Console.WriteLine("--- 文字列(string)のみ抽出 ---");
// string 型の要素だけを取り出す
// 戻り値は IEnumerable<string> となる
var stringList = mixedData.OfType<string>();
foreach (var str in stringList)
{
Console.WriteLine(str);
}
Console.WriteLine("\n--- 整数(int)のみ抽出 ---");
// int 型の要素だけを取り出す
var intList = mixedData.OfType<int>();
foreach (var num in intList)
{
Console.WriteLine(num);
}
}
}
出力結果:
--- 文字列(string)のみ抽出 ---
SystemLog
UserError
--- 整数(int)のみ抽出 ---
404
500
OfType と Cast の違い
LINQには型変換を行うメソッドとして、OfType<T>の他にCast<T>があります。これらは似ていますが、挙動が決定的に異なります。
OfType<T>: 型変換できる要素だけを抽出します。変換できない要素が含まれていても例外は発生せず、無視されます。フィルタリング用途に適しています。Cast<T>: コレクションのすべての要素を指定した型に変換しようとします。一つでも変換できない要素が含まれていると、InvalidCastException例外が発生し、処理が停止します。
データの中に「異物」が混じっている可能性がある場合は、安全なOfType<T>を使用するのが適切です。
応用:継承関係のあるクラスのフィルタリング
OfTypeはプリミティブ型(int, string)だけでなく、クラスの継承関係に基づいたフィルタリングにも非常に有効です。
例えば、基底クラスのリストに様々な派生クラスが格納されている状態で、特定の派生クラスだけを処理対象にしたい場合などに利用されます。
コード例2:特定の派生クラスのみを処理
「支払い方法(Payment)」という基底クラスと、それを継承した「クレジットカード」「銀行振込」クラスがある状況を想定します。
using System;
using System.Collections.Generic;
using System.Linq;
// 基底クラス
public abstract class Payment { }
// 派生クラス1
public class CreditCardPayment : Payment
{
public string CardNumber { get; set; } = "";
}
// 派生クラス2
public class BankTransferPayment : Payment
{
public string BankName { get; set; } = "";
}
public class OfTypeInheritanceExample
{
public static void Main()
{
// Payment 型のリストに、異なる派生クラスが混在している
var payments = new List<Payment>
{
new CreditCardPayment { CardNumber = "1111-xxxx" },
new BankTransferPayment { BankName = "City Bank" },
new CreditCardPayment { CardNumber = "2222-xxxx" }
};
// ここから「クレジットカード払い」のデータだけを抽出したい
// OfType<CreditCardPayment> を使用することで、
// 型チェックとキャストを同時に行い、CreditCardPayment のリストとして扱える
var creditPayments = payments.OfType<CreditCardPayment>();
Console.WriteLine("--- クレジットカード決済一覧 ---");
foreach (var card in creditPayments)
{
// card変数は CreditCardPayment 型になっているため
// CardNumber プロパティに直接アクセス可能
Console.WriteLine($"カード番号: {card.CardNumber}");
}
}
}
出力結果:
--- クレジットカード決済一覧 ---
カード番号: 1111-xxxx
カード番号: 2222-xxxx
まとめ
OfType<T>メソッドは、コレクションに対する「型によるフィルタリング」と「キャスト」を同時に行う強力な機能です。
- 安全性: 型が一致しない要素はスキップされるため、例外が発生しません。
- 簡潔性:
foreachループ内でif (obj is T)と書く必要がなくなり、コードがシンプルになります。 - 用途:
object配列の解析や、多態性(ポリモーフィズム)を持つコレクションから特定のサブクラスを抽出する際に最適です。
