疑問メモ: IEnumerable<>の派生クラスで実装すべきGetEnumerator

次の本を読んでいたら、C#の言語機能を解説した章で、分からない箇所がありました。

2つのModelクラスがあります。1つは、Product。もう1つは、Listをメンバに持つShoppingCartです。

ShoppingCartは、IEnumerable<>を拡張しています。

Product.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace LanguageFeatures.Models
{
    public class Product
    {
        public int ProductID { get; set; }
        public int Name { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
        public string Category { get; set; }
    }
}

ShoppingCart.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace LanguageFeatures.Models
{
    public class ShoppingCart: IEnumerable<Product>
    {
        public List<Product> Products { get; set; }

        // 1つ目のGetEnumerator
        public IEnumerator<Product> GetEnumerator() {
            return Products.GetEnumerator();
        }

        // 2つ目GetEnumerator
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}

ShoppingCartクラスに、GetEnumeratorメソッドが2つ出てきます。

1つ目のGetEnumeratorがIEnumerator、2つ目のGetEnumeratorがIEnumeratorを返す実装ですよね。

なぜ2つの実装が必要なのかが分かりません。

検索したら解説記事を見つけました。

IEnumerable を継承するには GetEnumerator()メソッドを実装します。

IEnumerator GetEnumerator();

加えて、 IEnumerator の GetEnumerator() メソッドも実装する必要があります。

IEnumerator GetEnumerator()

これは IEnumerator が単に IEnumerator のジェネリック版というだけでなく、 IEnumerator を継承しているインターフェースだからです。

IEnumerator を継承すると IEnumerator も継承することになります。

実際にはIEnumerator の GetEnumerator() しかまず使われません。 しかし、両方の GetEnumerator() メソッドを実装しないとコンパイルに失敗してしまいます。

C# インターフェース - IEnumerable(T) | プログラマーズ雑記帳

GetEnumeratorが2つ必要な理由が、分かりそうで分かりません分かったので、追記します。smogamiさん、tsurumaruさんにTwitterで教えていただきました。ありがとうございます。

  • 2つ目のGetEnumeratorが内部で呼び出しているGetEnumeratorが、1つ目のGetEnumeratorではないかと推測するが、それは正しいか → 正しい
  • もし正しければ、「GetEnumerator()」と書くだけで、1つ目、つまりIEnumeratorを返す方のメソッドが呼ばれるのはなぜか
  • もし正しくなければ、2つ目のGetEnumeratorが内部で呼び出しているGetEnumeratorは何をしているのか

IEnumeratorとIEnumeratorが、GetEnumeratorという同じ名前のメソッドを持つため、どちらの実装なのかを特定してやる必要があります。そのために、2つ目のような「IEnumerable.GetEnumerator()」という書き方でメソッドを実装をします。

このようにインタフェース名をつけて実装したメソッドは、インタフェースを介してしかアクセスできなくなります。したがって、2つ目のメソッドが呼び出しているのは、インタフェース名を特定しない実装、すなわち1つ目のGetEnumerator()です。

smogamiさんに教えてもらったページがとても分かりやすいです。

(追記ここまで)

ちなみに、利用側のコードは、IEnumerableをforeachで回すExtension Methodです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace LanguageFeatures.Models
{
    public static class MyExtensionMethods
    {
        public static decimal TotalPrice(this IEnumerable<Product> productEnum)
        {
            decimal total = 0;
            foreach (Product prod in productEnum)
            {
                total += prod.Price;
            }
            return total;
        }
    }
}