制約含めた generic で死亡 - どう書けばえぇねん…?

こんなコード良く書きますよね。Collection を foreach で1個ずつ取っていくっていうただそれだけなんですけれども。

List<Rozen> rozenList = GetRozenList();
foreach(Rozen r in rozenList)
{
  hoge(r);
}

で、これ何が困るって rozenList が null だと foreach で ぬるぽ*1 っちゃう事なんですよね。 null だったら要素0のListと解釈してそのままループをスルーして先に進んで欲しかったりするわけですよ。



なもんで、foreach の前に if(rozenList != null) とか書始めるわけですが…

List<Rozen> rozenList = GetRozenList();
if(rozenList != null)
{
  foreach(Rozen r in rozenList)
  {
    hoge(r);
  }
}


// こうする事もあるかなぁー
List<Rozen> rozenList = GetRozenList();
if(rozenList == null)
  rozenList = new List<Rozen>();
foreach(Rozen r in rozenList)
{
  hoge(r);
}

……なんで毎回 if で分岐せなアカンわけですか。めんどうじゃ! …ってことで、関数を一つ設けて「引数が null だったら、要素0のリストを返す。そうじゃなければそのまま返す」と定義しようと考えたわけですよ。

List<Rozen> rozenList = GetRozenList();
foreach(Rozen r in safeCollection(rozenList))
{
  hoge(r);
}

// 関数用意
public T safeCollection<T>(T t)
  where T : new T()
{
  return t != null ? t : new T();
}

ちょっとコストはかかるけど、if 書かなくて済むようになったので個人的には満足。




…とはいえ、safeCollection() がコレクション以外を受け入れちゃうのがちょっと気持ち悪いワケです。型引数に制約入れたいよねーと。 ……で、ココで詰まる(ぉ ICollection 制約を入れた場合、どう関数を定義すりゃいいのかが全く分からず。

// ICollection<T> の型引数は T じゃない!
// → この関数の T は List<Rozen> なので ICollection<List<Rozen> >に化けてしまう
public T safeCollection<T>(T t)
  where T : ICollection<T> new T()
{
  return t != null ? t : new T();
}

// CS0307: 型パラメータ 'T' は型引数と一緒には使用できません。
public T<U> safeCollection<T, U>(T<U> t)
  where T : ICollection<U>, new T()
{
  return t != null ? t : new T();
}

// 色々エラーで当然通らなかった
public T<U> safeCollection<T<U>, U>(T<U> t)
  where T : ICollection<U>, new T()
{
  return t != null ? t : new T();
}

// 通った! …と思ったら、関数呼び出し元で
// CS0411: 型引数を使い方から推論することはできません。型引数を明示的に指定してください。
public static T safeCollection<T, U>(T t)
  where T : ICollection<U>, new()
{
  return t != null ? t : new T();
}

// 一応通るけど、generic版の ICollection<T> の使いたいよね…
public static T safeCollection<T>(T t)
  where T : System.Collections.ICollection, new()
{
  return t != null ? t : new T();
}


// ちなみに Java ではこう書いた
@SuppressWarnings(value={"unchecked"})
static public <T extends Collection<U>, U> T safeCollection(T t)
{
  return t != null ? t : (T)Collections.emptyList();
}

…さぁ困ったじぇ…

*1:C# だから 'ぬるり' だけど