none
C# 時系列処理用配列クラス(Serise)を作成中ですがIEnumerableのエラーを解消でません。 RRS feed

  • 質問

  • C# 時系列処理用配列クラス(Serise)を作成中ですがIEnumerableのエラーを解消でません。
    【実現したいこと】
    1、動的配列にAdd()すると[0]に追加される(様に見えても可)、[0]を読めば最後に追加した値を取得できる。
    2、foreachでも最後に追加した値(Array[0])から順に列挙したい。
    3、Linqでも2と同じ挙動を実現したい
    4、必須ではないが、2,3以外の列挙処理も同じ挙動にできればいいなと思ってる
    5、処理速度を重視している

    【行ったこと】
    ベースとする入れ物は List<T> を使うことにした。(この選択が正しいのか不明)

    public interface ISeries<T>
    {
    	T this[int DisIndex] { get; }
    	int Count { get; }
    }
    
    public class Series<T> : List<T>, ISeries<T>
    {
    	public new T this[int i]
    	{
    		get { return base[base.Count - 1 - i]; }
    		set { base[base.Count - 1 - i] = value; }
    	}
    
    	public new IEnumerator<T> GetEnumerator()
    	{
    		for (int i= 0; i<base.Count; i++ ) yield return this[i];
    	}
    
    	IEnumerator IEnumerable.GetEnumerator()
    	{
    		return this.GetEnumerator();
    	}
    }

    【結果】
    コンパイルすると
    エラー 1 'Series<T>.IEnumerable.GetEnumerator()': 含む型は、インターフェイス 'System.Collections.IEnumerable' を実装しません。


    次に
    // IEnumerator IEnumerable.GetEnumerator()
    // {
    // return this.GetEnumerator();
    // }
    ここをコメントアウトするとコンパイルは通る。
    実行すると

    var s = Series<string>();
    
    // 常に最後に追加した値が s[0] に入る。
    s.Add("A"); // s[0] = "A"
    s.Add("B"); // s[0] = "B", s[1] = "A"
    s.Add("C"); // s[0] = "C", s[1] = "B", s[2] = "A"
    
    for (var i = 0; i < s.Count; i++) Console.WriteLine("{0} : {1}", i, s[i]);
    foreach (var v in s) Console.WriteLine("{0}", v);
    s.ForEach(v => Console.WriteLine("{0}", v));

    出力

    // for
    0 : C
    1 : B
    2 : A
    // foreach
    C
    B
    A
    // s.ForEach
    A
    B
    C

    for, foreach は期待通りの挙動になった。
    s.ForEachは違う挙動になった。
    LINQ は使えなかった。

    【アドバイスいただきたい事】
    1、【実現したいこと】の1~3を実現するためにやらなければならない事
    2、1~5全てを満たすためにやらなければならない事


    まだC#初心者(プログラミング初心者)でサイトの情報を頼りにプログラムしています。
    このエラーを解決できる情報に行きつけずませんでしたorz
    アドバイスよろしくお願いします。



    • 編集済み bakabon88 2015年5月22日 5:56
    2015年5月22日 5:47

回答

  • こんにちは。

    Listが前提になりますか?
    であればこの回答は無視して頂いて結構です。

    Stack<T>をベースとして考えてはどうだろう?と思いました。
    インデクサとForEachだけ実装してやれば要件は満たせそうですがどうでしょう?

        class Series<T> : Stack<T>
        {
            public T this[int index]
            {
                get
                {
                    return this.ElementAt(index);
                }
            }
            public void ForEach(Action<T> action)
            {
                if (action == null)
                {
                    //Exception
                }
                foreach(var current in this)
                {
                    action(current);
                }
            }
        }

    ※本格的なListとして使いたいのであればForeach以外はIList等で実装したほうが良いですね。

    Listベースのものも少し考えてみます。


    2015年5月22日 6:56
    モデレータ
  • おっと、少し抜けていました。

        public class Series<T> : List<T>, ISeries<T>, IEnumerable<T>, IEnumerable
        {
            public new T this[int i]
            {
                get { return base[base.Count - 1 - i]; }
                set { base[base.Count - 1 - i] = value; }
            }
            IEnumerator<T> IEnumerable<T>.GetEnumerator()
            {
                for (int i = 0; i < base.Count; i++) yield return this[i];
            }
            IEnumerator IEnumerable.GetEnumerator()
            {
                return this.GetEnumerator();
            }
        }

    こんな感じではどうでしょうか?

    ※IEnumerable<T>も実装したわけですね。

    --追記

    何度もすみません、ちょっと勘違いしてました。

    ForEachってList<T>自身が実装してるメソッドですね…

    これだと、Foreachをnewで隠蔽しないとダメそうな感じがしますね…(その場合もList<T>にキャストしてるとダメですが)

    • 編集済み なちゃ 2015年5月22日 10:26
    • 回答としてマーク bakabon88 2015年5月22日 10:27
    2015年5月22日 9:52
  • >(その場合もList<T>にキャストしてるとダメですが)

    この意味を確かめるために、List<T>にキャストしてみたら for, foreach, ForEach は元のListの挙動に戻ったのですが

    不思議にもLINQはSeriseの挙動のままでした。。

    List<T>にキャストすることで、for、foreach、ForEachはそれぞれ、newで隠蔽される前の、this[]、GetEnumerator、ForEachメソッドを利用することになります。

    よって、その挙動はList<T>の挙動に戻ります。

    一方で、LINQのSelectは、IEnumerable<T>の拡張メソッドですので、IEnumerable<T>型を起点として動作することになります。

    結果として、LINQでは、List<T>ではなく、IEnumerable<T>にキャストしたのと同じことになります(IEnumerable<T>のSelect拡張メソッドに、IEnumerable<T>にキャストされたSerise<T>が渡されるイメージ)。

    Serise<T>はIEnumerable<T>を実装しなおしているため、IEnumerable<T>にキャストすると、Serise<T>で明示実装しなおした版のIEnumerable<T>.GetEnumeratorが使用されることになります。


    • 編集済み なちゃ 2015年5月22日 11:42
    • 回答としてマーク bakabon88 2015年5月22日 14:00
    2015年5月22日 11:41
  •     public class Series<T> : List<T>, ISeries<T>, IEnumerable

    としたらどうなりますか?

    --追記

    インターフェイスの(明示)実装を行う場合は、その(明示)実装するクラス自身の定義でインターフェイスを直接実装しているような記述にする必要があります。

    ここでは、Series<T>自身がIEnumerableを実装するような書き方にする必要があります。

    • 編集済み なちゃ 2015年5月22日 7:25
    • 回答としてマーク bakabon88 2015年5月22日 9:33
    2015年5月22日 7:21

すべての返信

  • こんにちは。

    Listが前提になりますか?
    であればこの回答は無視して頂いて結構です。

    Stack<T>をベースとして考えてはどうだろう?と思いました。
    インデクサとForEachだけ実装してやれば要件は満たせそうですがどうでしょう?

        class Series<T> : Stack<T>
        {
            public T this[int index]
            {
                get
                {
                    return this.ElementAt(index);
                }
            }
            public void ForEach(Action<T> action)
            {
                if (action == null)
                {
                    //Exception
                }
                foreach(var current in this)
                {
                    action(current);
                }
            }
        }

    ※本格的なListとして使いたいのであればForeach以外はIList等で実装したほうが良いですね。

    Listベースのものも少し考えてみます。


    2015年5月22日 6:56
    モデレータ
  •     public class Series<T> : List<T>, ISeries<T>, IEnumerable

    としたらどうなりますか?

    --追記

    インターフェイスの(明示)実装を行う場合は、その(明示)実装するクラス自身の定義でインターフェイスを直接実装しているような記述にする必要があります。

    ここでは、Series<T>自身がIEnumerableを実装するような書き方にする必要があります。

    • 編集済み なちゃ 2015年5月22日 7:25
    • 回答としてマーク bakabon88 2015年5月22日 9:33
    2015年5月22日 7:21
  • レスありがとうございます。

    Stack を使う方法ですね

    メソッド Add を追加すれば違和感はないですね

    	public void Add(T value)
    	{
    		this.Push(value);
    	}
    

    速度比較をやってみようと思います。

    ありがとうございました。

    2015年5月22日 7:33
  • Stackでの実装については、インデックスアクセスのパフォーマンスがやや気になるといえば木になるかもしれませんね。

    要素数が少ないならそれほど気にしなくても大丈夫かもしれませんが、これは用途によりますね。

    ※若干実装が面倒かもしれませんが、本当は継承よりもコンポジションで実装するほうが望ましいと思います。

    2015年5月22日 7:34
  • なちゃさんこんにちは

    コンポジション?? 初めて聞く単語で調べてみたのですが

    public class Serise<T> : ISeries<T>
    {
    	private List<T> _list = new List<T>();
    
    	public new T this[int i]
    	{
    		get { return _list[_list.Count - 1 - i]; }
    		set { _list[_list.Count - 1 - i] = value; }
    	}
    }
    
    こんな感じに内包してしまう方法でしょうか?


    2015年5月22日 8:32
  • Tak1waさんのおかげでForEachの実装方法がわかりました。

    ありがとうございました。

    速度比較をしてみたのですが

    ElementAt(i)が想像以上に遅く、Stackは用途として向かないようでした。

    データ件数は通常使いで1万件以下で、データ分析時には50万件くらいになる可能性があります。

    通常(速度要求する)用途では添え字番号でしか読み書きしません。

    小さな塊のデータで抜き出したときにLINQを想定しています。

    簡単にLINQが実装できないようであればLINQの必要性を見直す必要がありそうですね。

    2015年5月22日 8:55
  • なちゃさんの最初の書き込みを見落としていました。 ;

    すみませんでした。

    > public class Series<T> : List<T>, ISeries<T>, IEnumerable

    としたらどうなりますか?

    コンパイル通りました!

    LINQは書き方が悪いのか foreach と同じ結果になりませんでしたがエラーが取れただけでも大助かりです。

    >インターフェイスの(明示)実装を行う場合は、その(明示)実装するクラス自身の定義でインターフェイスを直接実装しているような記述にする必要があります。
    >ここでは、Series<T>自身がIEnumerableを実装するような書き方にする必要があります。

    このご指摘で方向が見えました。

    実装してみます。

    ありがとうございました。


    2015年5月22日 9:33
  • おっと、少し抜けていました。

        public class Series<T> : List<T>, ISeries<T>, IEnumerable<T>, IEnumerable
        {
            public new T this[int i]
            {
                get { return base[base.Count - 1 - i]; }
                set { base[base.Count - 1 - i] = value; }
            }
            IEnumerator<T> IEnumerable<T>.GetEnumerator()
            {
                for (int i = 0; i < base.Count; i++) yield return this[i];
            }
            IEnumerator IEnumerable.GetEnumerator()
            {
                return this.GetEnumerator();
            }
        }

    こんな感じではどうでしょうか?

    ※IEnumerable<T>も実装したわけですね。

    --追記

    何度もすみません、ちょっと勘違いしてました。

    ForEachってList<T>自身が実装してるメソッドですね…

    これだと、Foreachをnewで隠蔽しないとダメそうな感じがしますね…(その場合もList<T>にキャストしてるとダメですが)

    • 編集済み なちゃ 2015年5月22日 10:26
    • 回答としてマーク bakabon88 2015年5月22日 10:27
    2015年5月22日 9:52
  • 速度比較をしてみたのですが

    ElementAt(i)が想像以上に遅く、Stackは用途として向かないようでした。

    インデックスアクセスのパフォーマンスについては先に少しだけ触れましたが、ElementAtというのは内部で列挙子を使って逐次検索するイメージの動作になるので、インデックスアクセスのパフォーマンスとしては期待できないことになります。

    コンポジションについては、

    >こんな感じに内包してしまう方法でしょうか?

    はい、そういう意味です。

    まあ今回は比較的簡単な実装で実現できそうな感じがしてきましたので、コンポジションを使うのは余計面倒なだけかもしれないですね。

    2015年5月22日 9:59
  • >インターフェイスの(明示)実装を行う場合は、その(明示)実装するクラス自身の定義でインターフェイスを直接実装しているような記述にする必要があります。

    >ここでは、Series<T>自身がIEnumerableを実装するような書き方にする必要があります。

    と言うことは、Series<T>自身がIEnumerable<T>を実装するような書き方にする必要があります。

    と思い書き換えたら見事LINQが期待どおりの挙動になりました。

    うれしくて結果を報告しようとここに戻るとなちゃさんがすでに同じコードをレスして頂いてました。

    同じコードだったのでとても安心です。

    Tak1waさんのおかげで ForEach も実装でき、1~5すべて実装できました。

    ありがとうございました!!

    2015年5月22日 10:26
  • >これだと、Foreachをnewで隠蔽しないとダメそうな感じがしますね…(その場合もList<T>にキャストしてるとダメですが)

    new を付けて実装しました。

    >(その場合もList<T>にキャストしてるとダメですが)

    この意味を確かめるために、List<T>にキャストしてみたら for, foreach, ForEach は元のListの挙動に戻ったのですが

    不思議にもLINQはSeriseの挙動のままでした。

    // Serise に追加した実装 public new void ForEach(Action<T> action) { for (int i=0; i<base.Count; i++) action( this[i] ); } // LINQ検証 (怪しい検証コード)

    var list = (List<string>)serise; foreach (var v in list.Select((v, i) => i + ":" + v)) { Console.WriteLine("{0}", v); }

    キャストの件は目的の用途で支障有りませんが、この現象をコードのコメントに残しておきたいと思います。

    2015年5月22日 11:07
  • >(その場合もList<T>にキャストしてるとダメですが)

    この意味を確かめるために、List<T>にキャストしてみたら for, foreach, ForEach は元のListの挙動に戻ったのですが

    不思議にもLINQはSeriseの挙動のままでした。。

    List<T>にキャストすることで、for、foreach、ForEachはそれぞれ、newで隠蔽される前の、this[]、GetEnumerator、ForEachメソッドを利用することになります。

    よって、その挙動はList<T>の挙動に戻ります。

    一方で、LINQのSelectは、IEnumerable<T>の拡張メソッドですので、IEnumerable<T>型を起点として動作することになります。

    結果として、LINQでは、List<T>ではなく、IEnumerable<T>にキャストしたのと同じことになります(IEnumerable<T>のSelect拡張メソッドに、IEnumerable<T>にキャストされたSerise<T>が渡されるイメージ)。

    Serise<T>はIEnumerable<T>を実装しなおしているため、IEnumerable<T>にキャストすると、Serise<T>で明示実装しなおした版のIEnumerable<T>.GetEnumeratorが使用されることになります。


    • 編集済み なちゃ 2015年5月22日 11:42
    • 回答としてマーク bakabon88 2015年5月22日 14:00
    2015年5月22日 11:41
  • 何度も読み返してこの不思議な挙動の意味がようやく分かりました。

    >Serise<T>はIEnumerable<T>を実装しなおしているため、IEnumerable<T>にキャストすると、Serise<T>で明示実装しなおした版のIEnumerable<T>.GetEnumeratorが使用されることになります。

    msdnのList<T>説明と見比べながら意味が分かってきました

    キャストしてもSeriseの挙動を維持しようと思えばIList<T>を明示実装すればいいのだということが見えてきました。

    インターフェースについて漠然としてた部分が薄らと霧が晴れてきたかのような気分です。

    ありがとうございました。

    2015年5月22日 14:00