简体   繁体   中英

C# implementing generic IEnumerable

I have been wondering if it is possible to make say State class implementing IEnumerable<Person> and IEnumerable<City> so I could get all the people living in the state via foreach as well as all the cities. It won't even compile saying this: Error 1 'ConsoleApplication1.City' does not implement interface member 'System.Collections.IEnumerable.GetEnumerator()' (weird)... Here is the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Collections;

namespace ConsoleApplication1
{
    class Person
    {
    }

    class City : IEnumerable<Person>
    {
        // City has citizens:
        Person[] citizens;

        IEnumerator<Person> IEnumerable<Person>.GetEnumerator()
        {
            foreach (Person p in citizens)
                yield return p;
        }
    }

    class State : IEnumerable<Person>, IEnumerable<City>
    {
        // State has cities:
        City[] cities;

        IEnumerator<Person> IEnumerable<Person>.GetEnumerator()
        {
            foreach (City c in cities)
                foreach (Person p in c)
                    yield return p;
        }

        IEnumerator<City> IEnumerable<City>.GetEnumerator()
        {
            foreach (City c in cities)
                yield return c;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            State s = new State();
            foreach (Person p in s) ;
            foreach (City c in s) ;
        }
    }
}

The problem is that IEnumerable<T> also requires you to implement IEnumerable (the non generic version). You need to implement both GetEnumerator() calls.

That being said, this will get very tricky, as you're State class will need to determine which thing to enumerate. I would, personally, avoid implementing IEnumerable<T> twice in one class, and instead return the enumerable as a method:

class State : IEnumerable<City>
{
    public IEnumerable<Person> GetPeople()
    {
      // return people...

In general, I think that trying to make something be an enumeration of two separate types is really a design flaw. It would be better to have State be implemented more like:

public class State
{
    public IEnumerable<City> Cities { get { // return cities...

    public IEnumerable<People> People { get { // return people...

This would require you to change your usage (slightly), to be more like:

foreach(Person person in theState.People)
{
    // ....

Personally, I think this would be a better approach for both State and City . I would write this like:

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

namespace ConsoleApplication1
{

    class Person
    {
    }

    class City
    {
        // City has citizens:
        Person[] citizens;

        public IEnumerable<Person> People
        {
            get
            {
                return citizens;
            }
        }
    }

    class State : IEnumerable<Person>, IEnumerable<City>
    {
        // State has cities:
        City[] cities;

        public IEnumerable<City> Cities
        {
            get
            {
                return cities;
            }
        }

        public IEnumerable<Person> AllPeople
        {
            get
            {
                return Cities.SelectMany(c => c.People);
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            State s = new State();
            foreach (Person p in s.AllPeople) { /* Do something */ }
            foreach (City c in s.Cities) { /* Do something */ } 
        }
    }
}

I find this is much more clear - as a City has People, but it is not, itself, People, etc.

You need to implement the non-generic variants, too! System.Collections.IEnumerable is the one without the generic type argument!

Add one explicit interface implementations for the System.Collections.Generic.IEnumerable.GetEnumerator - and make it throw an exception - there is no way you can implement the non-generic interface correctly.

This is actually an interesting question, because for the reason that both Reed and Sebastian point out, you need to implement the non-generic methods as well. However, you can only implement it once, and you have two interfaces that require. I think a better design might be not have state implement IEnumerable at all, but have two properties People and Cities that expose IEnumerables. I think this would also be a slightly more convenient (discoverable) API.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM