【C#】foreachからIEnumerableとIEnumeratorの必要性。

  • 2009.02.25 Wednesday
  • 23:00
Source and Project

【C#】foreach と for と頭グチャグチャ。その2でList構造でかつforeachで使用可能な集団型を作成してみました。タイトルの通り頭もぐちゃぐちゃですが、実装もぐちゃぐちゃでした。ということで今回はこれに手を加えて、必要最低限の情報しか公開しないまさにforeachで回すためだけの集団型にしていこうと思います。

まず前回の実装に難癖をつけてみることにします。下記はforeach文を実装する側の視点から見ての問題点です。

1.クラスCのオブジェクトが実際にforeachで使用できるのか比較的に明示的でない。
2.クラスCのオブジェクトの要素型が明示的にしめせていない。
  C → E → ListItemの順にクラスの宣言を追わないといけない。
3.要素型ListItemのValueプロパティを使用しているため、
  ListItemクラスの構造を理解しておかなければならない。
4.ListItem.Valueプロパティがobject型を返すのは何となく嫌だ。
5.列挙型Eをコード中に書く必要はないが、存在自体は知っておかなければならない。


さて、これを問題解決するには、どこからはじめるか・・・始めやすいものから行き当たりばったりで行ってみましょう!とりあえず思いつくのが、foreachで回すのだからIEnumerableとIEnumeratorまたはジェネリックIEnumerableとジェネリックIEnumeratorを実装することですか!すでにforeachで回すための能力はあるので、インターフェイスを継承するのは簡単です。

今回はIEnumerable<ListItem>とIEnumerator<ListItem>を継承するようにしてみました。

まずはクラスEから

 internal class E : IEnumerator<ListItem>
 {
  public E(ListItem item)
  {
   _top = item;
  }

  public bool MoveNext()
  {
   if (_current == null)
    Reset();
   else
    _current =_current.Next;
   return _current != null;
  }

  public void Reset()
  {
   _current = _top;
  }

  public ListItem Current
  {
   get { return _current; }
  }

  object IEnumerator.Current
  {
   get { return Current; }
  }

  public void Dispose()
  {
  }

  private readonly ListItem _top;
  private ListItem _current;
 }

ポイントはpublicなIEnumerator<ListItem>を継承しているのでEクラス自身はユーザに公開する必要がなくなるという点です。すでにこれでEクラスの隠蔽化に成功しました。ユーザはというよりforeachはMoveNextメソッドとpublicなCurrentプロパティだけを使います。

次に、集団型CクラスのIEnumerable<ListItem>で継承します。ポイントは

public IEnumerator<ListItem> GetEnumerator()

上記のメソッドを公開すること、これでforeachの型を明示できるため、

   foreach (var item in c)
    Console.WriteLine(item.Value);

というように、varを使用してもValueプロパティが使用できます。これが、

public IEnumerator GetEnumerator()

と公開してしまうと、varでは型がobject型になってしまいます。明示的にforechに型を指定しないといけません。そもそもわかりづらくなります。ということで要素型を公開するためにも

public IEnumerator<ListItem> GetEnumerator()

が良いかと、

さて、ここでもうひとつうれしい事が起こります。C#3.0で追加となった仕様。Collection-Initializer(コレクション初期化子)が使えるようになります。Collection-InitializerはIEmerableを継承するクラスにAddメソッドを実装していると使えます。

下記のような感じで使えます。

C c = new C { "さて", "今日の", "飯は", "何かな?" };

Add(...); Add(...);と記述するよりもコードがすっきりします。

というところで取りあえず止めます。

実際にコードの全容を見てみると


using System.Collections;
using System.Collections.Generic;
using System;

namespace Art55.Lib
{
 public class C : IEnumerable<ListItem>
 {
  public void Add(object value)
  {
   ListItem tail = _head.GetTail();
   if (_head == null)
    _head = new ListItem(value);
   else
    tail.Next = new ListItem(value);
  }

  public IEnumerator<ListItem> GetEnumerator()
  {
   return new E(_head);
  }

  IEnumerator IEnumerable.GetEnumerator()
  {
   foreach (var item in this)
    yield return item;
  }
  
  private ListItem _head;
 }

 internal class E : IEnumerator<ListItem>
 {
  public E(ListItem item)
  {
   _top = item;
  }

  public bool MoveNext()
  {
   if (_current == null)
    Reset();
   else
    _current =_current.Next;
   return _current != null;
  }

  public void Reset()
  {
   _current = _top;
  }

  public ListItem Current
  {
   get { return _current; }
  }

  object IEnumerator.Current
  {
   get { return Current; }
  }

  public void Dispose()
  {
  }

  private readonly ListItem _top;
  private ListItem _current;
 }

 public class ListItem
 {
  public ListItem(object value)
  {
   Value = value;
  }

  public override string ToString()
  {
   return Value == null ? string.Empty : Value.ToString();
  }

  internal protected ListItem Next { get; set; }
  public object Value { get; private set; }
 }

 static class ListItemUtil
 {
  public static ListItem GetTail(this ListItem head)
  {
   if (head == null)
    return null;
   while (head.Next != null)
    head = head.Next;
   return head;
  }
 }
}

--------------------------
Mainメソッドは下記のとおり

 class Program
 {
  static void Main(string[] args)
  {
   C c = new C { "さて", "今日の", "飯は", "何かな?" };

   foreach (var item in c)
    Console.WriteLine(item.Value);
  }
 }

実行すると

foreach

IEnumerable<ListItem>とIEnumerator<ListItem>を継承することで、列挙型Eの隠ペイ化、それからプログラマに対してforeachでの要素型の特定が容易になりました。ただし、まだ問題があります。そもそもListItemが余計です。これは、単にList構造をとるためだけに用意したクラスなので、foreach文を使用する人間には不要な情報です。なので今度はこれを隠ぺい化します。
あらにValueを廃止して代わりにジェネリックTを用意します。

実際に変更してみると

using System.Collections;
using System.Collections.Generic;

namespace Art55.Lib
{
 public class C<T> : IEnumerable<T>
 {
  public void Add(T value)
  {
   ListNode<T> tail = ListItemUtil<T>.GetTail(_head);
   if (_head == null)
    _head = new ListNode<T>(value);
   else
    tail.Next = new ListNode<T>(value);
  }

  public IEnumerator<T> GetEnumerator()
  {
   return new E<T>(_head);
  }

  IEnumerator IEnumerable.GetEnumerator()
  {
   foreach (var item in this)
    yield return item;
  }

  private ListNode<T> _head;
 }

 internal class E<T> : IEnumerator<T>
 {
  public E(ListNode<T> item)
  {
   _top = item;
  }

  public bool MoveNext()
  {
   if (_current == null)
    Reset();
   else
    _current =_current.Next;
   return _current != null;
  }

  public void Reset()
  {
   _current = _top;
  }

  public T Current
  {
   get { return _current.Value; }
  }

  object IEnumerator.Current
  {
   get { return Current; }
  }

  public void Dispose()
  {
  }

  private readonly ListNode<T> _top;
  private ListNode<T> _current;
 }

 internal class ListNode<T>
 {
  public ListNode(T value)
  {
   Value = value;
  }

  //public override string ToString()
  //{
  // return Equals(Value, default(T)) ? string.Empty : Value.ToString();
  //}

  internal protected ListNode<T> Next { get; set; }
  public T Value { get; private set; }
 }

 static class ListItemUtil<T>
 {
  public static ListNode<T> GetTail(ListNode<T> head)
  {
   if (head == null)
    return null;
   while (head.Next != null)
    head = head.Next;
   return head;
  }
 }
}
これで集団型C<T>クラスは要素Tを列挙する能力のあるクラスということだけが自明なクラスになりました。内部的なList構造も隠蔽かしています。ただし、要素が増えれば増えるほど、Addの速度が遅くなるは手をつけていません。単にtailをキャッシュすればいいだけなんですが(笑)

それではMainメソッドで使用してみます。

  static void Main(string[] args)
  {
   var c = new C<string> { "さて", "今日の", "飯は", "何かな?" };

   foreach (var item in c)
    Console.WriteLine(item);
  }

とてもすっきりしたコードが書けるようになりました。要素型で指定したコレクションを列挙したという目的だけが見えるコードがかけていますね(自画自賛)


Source and Project

IEnumerableインターフェイスとIEnumeratorインターフェイスは重要ですね!

以上
コメント
管理者の承認待ちコメントです。
  • -
  • 2018/05/21 10:24 AM
コメントする








    
この記事のトラックバックURL
トラックバック

calendar

S M T W T F S
 123456
78910111213
14151617181920
21222324252627
28293031   
<< July 2019 >>

あわせて読みたい

あわせて読みたいブログパーツ

selected entries

categories

archives

recent comment

  • 【キーボード】6年前のRealForceを復活させることはできる!?その3
    art55 (05/22)
  • 【キーボード】6年前のRealForceを復活させることはできる!?その3
    分解大好き (05/18)
  • 【.NET Framework 4.5】 IListがIReadOnlyListを継承してない理由。
    art55 (02/04)
  • 【.NET Framework 4.5】 IListがIReadOnlyListを継承してない理由。
    Gen (02/04)
  • 【キーボード】RealForce が壊れて帰ってきた。
    art55 (04/29)
  • 【.NET Framework 4.5】 IListがIReadOnlyListを継承してない理由。
    art55 (02/23)
  • 【.NET Framework 4.5】 IListがIReadOnlyListを継承してない理由。
    かるあ (02/22)
  • 【C#】Dictionaryの実装・データ構造・アルゴリズムを観察する。
    art55 (01/16)
  • 【C#】Dictionaryの実装・データ構造・アルゴリズムを観察する。
    karuakun (01/16)
  • 【NetOffice】【Excel】死なないExcelプロセスをKillする。
    art55 (12/05)

recent trackback

recommend

recommend

recommend

C#プログラマのための.NETアプリケーション最適化技法 (Programmer's SELECTION)
C#プログラマのための.NETアプリケーション最適化技法 (Programmer's SELECTION) (JUGEMレビュー »)
Sasha Goldshtein,Dima Zurbalev,Ido Flatow,サシャ・ゴルドシュタイン,ディマ・ズルバレフ,イド・フラトー

recommend

ろんりと集合
ろんりと集合 (JUGEMレビュー »)
中内 伸光
とてもわかりやすいです。

recommend

recommend

シャノン・ノイマン・ディジタル世界
シャノン・ノイマン・ディジタル世界 (JUGEMレビュー »)
市川 忠男
4章がリレーショナルデータベースな内容になってます。ページ数があまりありませんが、ポイントがものすごく的確にまとまっていて、感動します。

recommend

recommend

東プレ Realforce91UBK-S 静音キーボード 静電容量無接点方式 変荷重 ブラック NG01BS
東プレ Realforce91UBK-S 静音キーボード 静電容量無接点方式 変荷重 ブラック NG01BS (JUGEMレビュー »)

テンキーレス、静音のRealForce91UBK-S。スコスコ感がたまらなく気持ちいいです。家と会社で2台持ってます。

recommend

recommend

プログラミング.NET Framework 第4版 (プログラミングシリーズ)
プログラミング.NET Framework 第4版 (プログラミングシリーズ) (JUGEMレビュー »)
Jeffrey Richter
発売予定美 2013年10月10日。.NET Frameworkとお付き合いする人のバイブルですね。

recommend

recommend

キャット・シッターの君に。
キャット・シッターの君に。 (JUGEMレビュー »)
喜多嶋 隆
私のイラストレータデビュー本です。

recommend

Essential .NET ― 共通言語ランタイムの本質
Essential .NET ― 共通言語ランタイムの本質 (JUGEMレビュー »)
ドン・ボックス,クリス・セルズ,Don Box,Chris Sells,吉松 史彰

links

profile

search this site.

others

mobile

qrcode

powered

無料ブログ作成サービス JUGEM