独自クラスのオブジェクトをリストに入れたりする処理を実装してて、タイトルのように、基底クラスのリストに派生クラスのオブジェクトが入ることに気付きました。そこで、自分なりに注意したいと思ったことを軽くまとめてみました。
まず、名前だけを持つUser
クラスと、これを継承して名前と住所を持つ派生クラスを用意します。
class User
{
internal string name { get; set; }
}
class User2 : User
{
internal string address { get; set; }
}
そして、User
クラスのリストを用意します。
List<User> userList = new List<User>();
ここで用意したuserList
にUser
クラスのオブジェクトが入るのは、まぁそりゃそうだよねと言った感じですね。
User user = new User
{
name = "testName"
};
userList.Add(user);
Console.WriteLine(userList[0].name); // testName
それでもって、同じuserList
にUser2
クラスのオブジェクトも入ってしまうということで、おやおや?と思った次第です。
User2 user2 = new User2
{
name = "testName2",
address = "testAddress"
};
userList.Add(user2);
Console.WriteLine(userList[1].name); // testName2
ただ、userList[1]
からUser2
クラスにしかないaddress
を参照しようとすると、コンパイラエラーが発生してしまいます。
Console.WriteLine(userList[1].address); // Compiler Error CS1061
基底クラスのオブジェクトと派生クラスのオブジェクト両方とも登場する可能性があるときは、型変換や型パターンマッチングを入れた方が良いと感じました。
型を変換する
使いたいオブジェクトのクラスが明確なら、キャスト式で変換する。
// userList[1]は、User2クラス!
Console.WriteLine(((User2)userList[1]).address); // testAddress
もしも、User
クラスのオブジェクトをUser2
クラスに変換しようとすると、System.InvalidCastException
が発生してしまう。
そのため、どのクラスのオブジェクトが来るか分からない時は型パターンマッチングも入れるべきだと思いました。
型パターンマッチング
User
クラスのオブジェクトはname
だけ出力、User2
クラスのオブジェクトは、name
とaddress
を表示させる。
foreach (User user in userList)
{
Console.WriteLine(user.name);
if (user is User2)
{
Console.WriteLine(((User2)user).address); // ちゃんとキャスト式で変換してあげる
}
}
※コメントで頂いた書き方をここに追記します。
foreach (User user in userList)
{
Console.WriteLine(user.name);
if (user is User2 user2) // ここで変数名を決められる
{
Console.WriteLine(user2.address);
}
}
何のクラスのオブジェクトなのか分かるような変数名にしてあげると、後の処理でどんなオブジェクトを扱ってるのか追いやすくなってイイ感じになりそうだなと思いました。
switchでも、case User2 user2:
のように、変数名を決められるようです。
最後に
なるべくキャスト式を用いることで、実装者がどんなオブジェクトが来る想定で実装したのか明確になるのではないかと感じました。また、型パターンマッチングも用いることで、より安全な実装になるのではないかと感じました。
Qiita初投稿です。記事の質はまだまだですが、アドバイスや意見などあれば、コメントを頂けると助かります。
今年はアウトプットを意識してエンジニアライフを楽しもうと思います。