トップ回答者
C# 時系列処理用配列クラス(Serise)を作成中ですがIEnumerableのエラーを解消でません。

質問
-
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
回答
-
こんにちは。
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ベースのものも少し考えてみます。
- 編集済み Tak1waMVP, Moderator 2015年5月22日 7:26
- 回答としてマーク bakabon88 2015年5月22日 9:17
-
おっと、少し抜けていました。
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>にキャストしてるとダメですが)
-
>(その場合も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が使用されることになります。
すべての返信
-
こんにちは。
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ベースのものも少し考えてみます。
- 編集済み Tak1waMVP, Moderator 2015年5月22日 7:26
- 回答としてマーク bakabon88 2015年5月22日 9:17
-
なちゃさんの最初の書き込みを見落としていました。 ;
すみませんでした。
> public class Series<T> : List<T>, ISeries<T>, IEnumerable
としたらどうなりますか?
コンパイル通りました!
LINQは書き方が悪いのか foreach と同じ結果になりませんでしたがエラーが取れただけでも大助かりです。
>インターフェイスの(明示)実装を行う場合は、その(明示)実装するクラス自身の定義でインターフェイスを直接実装しているような記述にする必要があります。
>ここでは、Series<T>自身がIEnumerableを実装するような書き方にする必要があります。このご指摘で方向が見えました。
実装してみます。
ありがとうございました。
-
おっと、少し抜けていました。
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>にキャストしてるとダメですが)
-
速度比較をしてみたのですが
ElementAt(i)が想像以上に遅く、Stackは用途として向かないようでした。
インデックスアクセスのパフォーマンスについては先に少しだけ触れましたが、ElementAtというのは内部で列挙子を使って逐次検索するイメージの動作になるので、インデックスアクセスのパフォーマンスとしては期待できないことになります。
コンポジションについては、
>こんな感じに内包してしまう方法でしょうか?
はい、そういう意味です。
まあ今回は比較的簡単な実装で実現できそうな感じがしてきましたので、コンポジションを使うのは余計面倒なだけかもしれないですね。
-
>インターフェイスの(明示)実装を行う場合は、その(明示)実装するクラス自身の定義でインターフェイスを直接実装しているような記述にする必要があります。
>ここでは、Series<T>自身がIEnumerableを実装するような書き方にする必要があります。
と言うことは、Series<T>自身がIEnumerable<T>を実装するような書き方にする必要があります。
と思い書き換えたら見事LINQが期待どおりの挙動になりました。
うれしくて結果を報告しようとここに戻るとなちゃさんがすでに同じコードをレスして頂いてました。
同じコードだったのでとても安心です。
Tak1waさんのおかげで ForEach も実装でき、1~5すべて実装できました。
ありがとうございました!!
-
>これだと、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); }
-
>(その場合も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が使用されることになります。
-
何度も読み返してこの不思議な挙動の意味がようやく分かりました。
>Serise<T>はIEnumerable<T>を実装しなおしているため、IEnumerable<T>にキャストすると、Serise<T>で明示実装しなおした版のIEnumerable<T>.GetEnumeratorが使用されることになります。
msdnのList<T>説明と見比べながら意味が分かってきました
キャストしてもSeriseの挙動を維持しようと思えばIList<T>を明示実装すればいいのだということが見えてきました。
インターフェースについて漠然としてた部分が薄らと霧が晴れてきたかのような気分です。
ありがとうございました。