- PR -

ジェネリック型へのキャスト

投稿者投稿内容
泥爺
会議室デビュー日: 2007/12/03
投稿数: 8
投稿日時: 2008-08-13 21:15
非ジェネリックなクラスをジェネリックなクラスに置き換えるにあたり
キャストで躓いています。

↓のコードのArrayListをList<T>を使用して書きなおしたいのですが
どのようなコードを書けばよいのでしょうか?
コード:
object obj = GetHoge();
if (obj is ArrayList)
{
    ((ArrayList)obj).Clear();
}




↓はコンパイルエラーですが、やりたいことはこんな感じです。
コード:
object obj = GetHoge();
if (obj is List<>)
{
    ((List<>)obj).Clear();
}



開発環境はVisualStudio2005,C#2.0です。
masa
大ベテラン
会議室デビュー日: 2004/10/28
投稿数: 161
投稿日時: 2008-08-13 21:39
新しいコレクションにつめかえるということではないですよね?

ArrayList list;
List<Hoge> typedList = new List<Hoge>();
foreach ( object obj in list ) {
  typedList.Add( obj as Hoge );
}

同じコレクションに格納したままの状態でキャスト操作を減らすためということでしたら「ラッパー」を作るのはどうでしょうか。

public class WrappedList<T> : IList<T> {

 public WrappedList( IList list ) {
  m_List = list;
 }
 private IList m_List;

 public void Add( T item ) {
  m_List.Add(item);
 }
 // 同様に IList<T> のプロパティ・メソッドを実装します
}

ArrayList list;
WrappedList<Hoge> typedList = new WrappedList<Hoge>( list );

typedList.Add( new Hoge() );


[ メッセージ編集済み 編集者: masa 編集日時 2008-08-13 21:40 ]
お だ
会議室デビュー日: 2008/04/02
投稿数: 14
投稿日時: 2008-08-13 22:19
こんな感じですか?
コード:
object obj = GetHoge(); // List<string> が戻される前提
if (obj is List<string>)
{
    ((List<string>)obj).Clear();
}


ジェネリック型は、<T> に指定する型が異なると、
違う型と認識されたはずです。
なので、GetHoge() の戻り値が List<string> の時や List<int> 等の様に
変化するなら、
コード:
object obj = GetHoge();
if (obj is List<string>)
{
    ((List<string>)obj).Clear();
}
else if (obj is List<int>)
{
    ((List<int>)obj).Clear();

}
・・・


となります。
Clear メソッドを呼び出したいだけなら、
System.Collections.IList でも問題無いと思います。
コード:
object obj = GetHoge();
if (obj is System.Collections.IList)
{
    ((System.Collections.IList)obj).Clear();
}

泥爺
会議室デビュー日: 2007/12/03
投稿数: 8
投稿日時: 2008-08-13 23:14
早速の返信ありがとうございます。

後だしで情報を追加してすみませんが、GetHogeの戻値について以下の制限があります。
・ArrayListからList<T>に代わるのは決定です。なので、ラッパーで対処することはできません。
・List<T>以外にもIListの実装を含みます。そこからList<T>のみを識別する必要があります。
現状では、IList<T>かIListを実装するクラスです。(移行中なので…、そのうちIList<T>のみになる予定です)
・<T>のとりうる型は非常に多く列挙するのは現実的でないです。

<T>の型を列挙せずにうまくまとめられるとよいと思ったのですが、良い方法はないのでしょうか?

あと、若干の期待をもって↓も試したのですがダメでした。まぁ、そうですよね。
コード:
if (obj is List<object>)
{
    //List<int>もList<string>もここには入らない…
    ((List<object>)obj).Clear();
}

なちゃ
ぬし
会議室デビュー日: 2003/06/11
投稿数: 872
投稿日時: 2008-08-14 00:25
コード:
if (obj.GetType().IsGenericType && obj.GetType().GetGenericTypeDefinition == typeof(List<>))
{
}


な感じで判定はできますけど、その後どう使うのかが問題です。
masa
大ベテラン
会議室デビュー日: 2004/10/28
投稿数: 161
投稿日時: 2008-08-14 09:25
完全に読み違えてましたね、私。

GetHoge はいろいろな型を返すけれど、
呼び出し元にはその型はわからないということでしょうか。

public static object GetHoge() {

  // 戻り値とするオブジェクトの型を変更。
  // List<Hoge> であるとは限らず、
  // どんな型が返ってくるかは呼び出し元には分からない。

  // ArrayList list = new ArrayList();
  List<Hoge> list = new List<Hoge>();

  list.Add( new Hoge( "abc" ) );
  list.Add( new Hoge( "def" ) );

  return list;

}

既存コードへの影響をおさえたいのでしょうが、
全く手を入れずにということは難しいですよね?

呼び出し側にはこういう型で欲しい、
それ以外は要らないといった目的があるでしょうから、
その期待される型ごとに「ラッパーを作成したりキャストしたりする」メソッドを用意し、
GetHoge の部分を目的に応じて置き換えてもらうのではいけませんか?

同じメソッドがいろいろな型を返すというのは使うほうにとってはつらいですね。
べる
ぬし
会議室デビュー日: 2003/09/20
投稿数: 1093
投稿日時: 2008-08-14 10:36
if(obj.GetType().FullName.StartsWith("System.Collections.Generic.List"))
{
((IList)obj).Clear();
}

とかじゃだめですか?

[ メッセージ編集済み 編集者: べる 編集日時 2008-08-14 10:50 ]
泥爺
会議室デビュー日: 2007/12/03
投稿数: 8
投稿日時: 2008-08-14 13:00
皆様、いろいろありがとうございます。
頂いた意見を参考にし、今後の対応とします。

まとめ(?)
○is演算子でGeneric型を判断できない。
GetType()でTypeオブジェクトを取得し、各プロパティを使用して判断する必要がある。
継承クラスの場合は、BaseTypeを使用して継承関係をさかのぼる必要がある。

○Genericパラメータが入っていないメソッドといえども画一的に呼び出す手段はない
非Generic型のインターフェースを実装していると、非Generic型にキャストすることで可能だが、
それ以外の場合は、パラメータのとりうる型の数だけキャスト構文を用意する必要がある。
List<T>はIListもIList<T>も実装しているのでClearが呼出可能だが、IList<T>しか実装していない場合は呼出ができない。

○Genericパラメータが決まらないとキャストはできない
Typeオブジェクトの判断より、List<T>のインスタンスであることが判明しても、例えば
void Fuga<T>(List<T> list)
のメソッドの引数に渡すことはできない。

間違っていたらご指摘ください。

スキルアップ/キャリアアップ(JOB@IT)