Возможно ли реализовать несколько IEnumerable в одном классе?

#c# #.net #ienumerable

#c# #.net #ienumerable

Вопрос:

Я хочу сделать что-то вроде этого:

 class T : IEnumerable<string>, IEnumerable<int>
{
    string [] _strings = new string [20];
    int[] _ints = new int[20];

    public T() { }

    IEnumerator<string> IEnumerable<string>.GetEnumerator()
    {
        foreach (string str in _strings)
            yield return str;
    }

    IEnumerator<int> IEnumerable<int>.GetEnumerator()
    {
        foreach (int i in _ints)
            yield return i;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }

}
//Using in code:
T t = new T();
foreach (int i in t)
   //to do something
foreach (string str in t)
   //to do stuff
  

Я хочу знать, есть ли способ реализовать это или нет. Может быть, есть хитрости?

Ответ №1:

Вам почти удалось реализовать оба интерфейса — вам просто нужно изменить не общую реализацию, чтобы показать, какую общую реализацию вы пытаетесь делегировать. Например:

 IEnumerator IEnumerable.GetEnumerator()
{
    return ((IEnumerable<int>)this).GetEnumerator();
}
  

Однако, поскольку вы реализуете более одного IEnumerable<T> интерфейса, вам нужно будет выполнить приведение в цикле foreach, чтобы показать, какой из них использовать:

 T t = new T();
foreach (int i in (IEnumerable<int>) t)
   //to do something
foreach (string str in (IEnumerable<string>) t)
  

Лично я бы настоятельно не советовал этого делать, если это возможно — это вызовет много путаницы у людей, читающих ваш код.

Подробности о том, как компилятор обрабатывает выражение для итерации в цикле, см. В разделе 8.8.4 спецификации языка C # foreach .

(Используя «обычную» реализацию интерфейса для одного из интерфейсов, вы могли бы предоставить своего рода «default» — но я не думаю, что это действительно улучшило бы ситуацию.)

Ответ №2:

Я бы предложил определить некоторые типы структур, единственным полем которых является экземпляр вашего типа класса, инициализируемый конструктором stuct , единственным открытым членом которого будет GetEnumerator , и который реализует IEnumerable<whatever> , вызывая соответствующий метод для вашего базового типа. Затем пусть ваш корневой класс реализует член, который возвращает новый экземпляр соответствующей структуры.

Например, если в вашем классе Foo был метод GetStringEnumerator, который возвращал тип, реализующий IEnumerable<String> , у вас может быть Foo .AsStrings возвращает структуру FooStringEnumerable, метод GetEnumerator которой называется Foo .GetStringEnumerator .

Обратите внимание, что, если ваша перечисляемая вещь будет структурой, а не классом, вы избежите необходимости создавать дополнительный экземпляр объекта в обычном случае, когда он используется в цикле «для каждого», поскольку и vb, и c # будут вводить метод GetEnumerator вместо приведения к IEnumerableили IEnumerable<T> .