C # — Зачем внедрять две версии Current при реализации интерфейса IEnumerable?

#c#

#c#

Вопрос:

Я предполагаю, что следующий пример дает наилучшую практику, которой мы должны следовать при реализации интерфейса IEnumerable.

https://learn.microsoft.com/en-us/dotnet/api/system.collections.ienumerator.movenext

Вот вопрос:

  1. Почему мы должны предоставлять две версии текущего метода?
  2. Когда версия ОДНА (object IEnumerator.Используется Current)?
  3. Когда используется ВТОРАЯ версия (public Person Current)?
  4. Как использовать PeopleEnum в инструкции foreach. // обновлено
 public class PeopleEnum : IEnumerator
{
    public Person[] _people;

    // Enumerators are positioned before the first element
    // until the first MoveNext() call.
    int position = -1;

    public PeopleEnum(Person[] list)
    {
        _people = list;
    }

    public bool MoveNext()
    {
        position  ;
        return (position < _people.Length);
    }

    public void Reset()
    {
        position = -1;
    }

    // explicit interface implementation
    object IEnumerator.Current /// **version ONE**
    {
        get
        {
            return Current;
        }
    }

    public Person Current     /// **version TWO**
    {
        get
        {
            try
            {
                return _people[position];
            }
            catch (IndexOutOfRangeException)
            {
                throw new InvalidOperationException();
            }
        }
    }
}
  

Ответ №1:

IEnumerator.Current Это явная реализация интерфейса.

Вы можете использовать его, только если приведете итератор к IEnumerator (что и делает фреймворк с foreach помощью). В других случаях будет использоваться вторая версия.

Вы увидите, что он возвращает object и фактически использует другую реализацию, которая возвращает Person .

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

Комментарии:

1. Хотя все это верно, вы делаете вывод, что приведение к IEnumerator встречается редко; это не так. Для IEnumerable требуется метод GetEnumerator(), который возвращает объект типа IEnumerator, приведенный как таковой. GetEnumerator(), в свою очередь, используется практически любым встроенным кодом, использующим IEnumerables, таким как foreach и большая часть Linq.

Ответ №2:

Пространная реализация IEnumerator больше не нужна:

 public class PeopleEnum : IEnumerable
{
    public Person[] _people;

    public PeopleEnum(Person[] list)
    {
        _people = list;
    }

    public IEnumerator GetEnumerator()
    {
        foreach (Person person in _people)
            yield return person;
    }
}
  

И чтобы еще больше перенести это в 21 век, не используйте не универсальный IEnumerable:

 public class PeopleEnum : IEnumerable<Person>
{
    public Person[] _people;

    public PeopleEnum(Person[] list)
    {
        _people = list;
    }

    public IEnumerator<Person> GetEnumerator()
    {
        foreach (Person person in _people)
            yield return person;
    }

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

Ответ №3:

Я подозреваю, что причина в том, что этот пример кода был получен из класса example, реализующего IEnumerator<T> — если бы класс example PeopleEnum реализовал IEnumerator<T> этот подход, потребовался бы: IEnumerator<T> наследует IEnumerator , поэтому вам нужно реализовать оба интерфейса при реализации IEnumerator<T> .

Реализация неуниверсального IEnumerator требует Current возврата object — строго типизированный IEnumerator<T> , с другой стороны, требует, чтобы Current возвращал экземпляр type T — использование явной и прямой реализации интерфейса является единственным способом выполнить оба требования.

Комментарии:

1. Я только что обнаружил, что вы на самом деле дали нужный мне ответ. — спасибо

Ответ №4:

Это сделано для удобства, например. используя PeopleEnum.Current типобезопасным способом в цикле while (p.MoveNext()), явно не выполняя перечисление foreach.

Но единственное, что вам нужно сделать, это реализовать интерфейс, вы могли бы сделать это неявно, если хотите, однако есть ли для этого причина? Если бы я хотел использовать MovePrevious в классе? Было бы здорово, если бы я передал (распаковал) объект Person?

Если вы думаете, что класс можно расширить с помощью большего количества методов манипулирования, Person Current — классная вещь.

Ответ №5:

Вторая версия не является частью интерфейса. Вы должны удовлетворять требованиям интерфейса.