IEnumerator<t></t>



  • In my opinion, method bool MoveNext() violates one of the principles of SOLID is the same as the method of transition to the next element and the indication of the end of the sequence. This raises problems when the terator is not used in the simplest case in one cycle foreach, but as an argument in the methods imposed. i.e. using the outside method, is transferred to an internal which causes MoveNext()♪ It appears that only an internal method will find out about the end of the sequence, and it will not be possible for the outside to know, except in the reversing parameter from the inside. But don't. bool from every method that comes! It'll make the code just terrifying. Much more logical would be IEnumerator Make another characteristic of the end. In fact, such information is available at all times in the heterogeneous class and is returned to the method of displacement.

    Example:

    public class Parser
    {
        void Parse(IEnumerator<char> enumerator)
        {
            // Допустим есть последовательности букв, разделенных пробелами
            enumerator.Reset();
            while (enumerator.MoveNext())
            {
                if (!char.IsWhiteSpace(enumerator.Current)) // пробелы пропускаем
                    ParseWord(enumerator); // не понятно в каком положении последовательность
                // Далее должна быть обработка итератора и анализ enumerator.Current
            }
        }
    
    void ParseWord(IEnumerator&lt;char&gt; enumerator)
    {
        // читаем пока буквы
        while (char.IsLetter(enumerator.Current))
        {
            // ...
            // если при переходе встретили конец последовательности, выходим
            if (!enumerator.MoveNext())
            {
                break;
                // Отсюда начнутся проблемы, т.к.выйдя из метода,
                // теряется информация о конце последовательности.
            }
        }
    }
    

    }

    As you can see, when the ParseWord method comes out, there are two situations - either we are on the symbol next to the word or we have crossed the word to the end of the sequence. The Parse method cannot cause MoveNext(), otherwise lose the symbol and when referring to the enumerator. Current risks getting an exception. It might have been thought that, by exception, the end of sequence could be determined, but it was not. First exception InvalidOperationException may arise for another reason, and secondly, in the case of the mass, it will be, and in the case of the mass List<T> or string - No.

    I also want to point out that in Java, I understand the situation is not much better. There is a combination of the method of transition and receipt of the element itself:

    Interface Iterator<E>
    boolean hasNext() - Returns true if the iteration has more elements. (In other words, returns true if next would return an element rather than throwing an exception.)
    E next() - Returns the next element in the iteration. Throws: NoSuchElementException - iteration has no more elements.

    Similarly, in the case of Java, we will acquire information on the end of sequence, but we will lose the opportunity to analyse the element itself.

    As a solution to this problem, the creation of a template for an arbitrary heterogeneist who will capture and retain the returns has been seen MoveNext() values followed by the presentation of properties.

    I would like to have this:

    public interface IEnumerator
    {
    object Current { get; }
    bool MoveNext();
    bool HasValue { get; } // то же что и MoveNext, но без перемещения
    void Reset();
    }

    The question is whether there is a real architectural problem of the interface or whether there may still be a solution that allows for the use of the terator in many of the applied methods, without giving the meaning derived from the methods used. MoveNext()?


    @VladD: I'm sorry, but I don't really get it. It didn't take long to figure out, but when we're probably talking about different things, I decided to spend time on my example. More specifically, two examples. 1 with an enlarged numerator and 2 with standard. And how surprised it was that they ended up almost identical - only three different.

    // Внимание!
    // Допущение: вложенный энумератор должен управляться только логикой класса-обертки и не должен учавствовать в других операциях.

    // Допустим в интерфейсе итератора существует еще одно свойство
    public interface IEnumeratorEx<out T> : IEnumerator<T>
    {
    // Признак конца последовательности
    // Он же то, что возвращает MoveNext()
    // Он же, означающий наличие значения, которое можно получить
    bool HasValue { get; }
    }

    // Класс с расширенным энумератором (+HasValue)
    public class FilteringEnumeratorEx<T> : IEnumeratorEx<T>
    {
    IEnumeratorEx<T> wrapped; // Оригинальная последовательность, подлежащая фильтрации
    Func<T, bool> filter; // Фильтр

    public T Current
    {
        get
        {
            // Если вложенный итератор не пройден до конца, значит текущий элемент был найден,
            // отфильтрован и должен быть возвращен
            if (HasValue)
                return wrapped.Current;
            else
                throw new InvalidOperationException("Энумератор достиг конца. Значение не может быть получено.");
        }
    }
    
    object IEnumerator.Current { get { return Current; } }
    
    public bool HasValue { get { return wrapped.HasValue; } }
    
    public FilteringEnumeratorEx(IEnumeratorEx&lt;T&gt; wrapped, Func&lt;T, bool&gt; filter)
    {
        this.wrapped = wrapped;
        this.filter = filter;
    }
    
    public bool MoveNext()
    {
        if (!HasValue)
            return false;
        while (wrapped.MoveNext() &amp;&amp; !filter(wrapped.Current))
            ;
        return HasValue;
    }
    
    public void Reset() { wrapped.Reset(); }
    
    public void Dispose() { wrapped.Dispose(); }
    

    }

    // Класс со стандартным энумератором
    public class FilteringEnumerator<T> : IEnumerator<T>
    {
    IEnumerator<T> wrapped; // Оригинальная последовательность, подлежащая фильтрации
    Func<T, bool> filter; // Фильтр

    // Поскольку вложенный энумератор не имеет отдельного признака конца, кэшируем последнюю MoveNext()
    // Иными словами, изобретаем костыли
    // Хотя, раз уж есть такое полезное свойство, то почему бы его не выставить наружу, пусть пользуются.
    public bool HasValue { get; private set; } // 1 отличие
    
    public T Current
    {
        get
        {
            // Если вложенный итератор не пройден до конца, значит текущий элемент был найден,
            // отфильтрован и должен быть возвращен
            if (HasValue)
                return wrapped.Current;
            else
                throw new InvalidOperationException("Энумератор достиг конца. Значение не может быть получено.");
        }
    }
    
    object IEnumerator.Current { get { return Current; } }
    
    public FilteringEnumerator(IEnumerator&lt;T&gt; wrapped, Func&lt;T, bool&gt; filter)
    {
        this.wrapped = wrapped;
        this.filter = filter;
        HasValue = true; // -1 элемент всегда должен позволять шагнуть дальше // 2 отличие
    }
    
    public bool MoveNext()
    {
        if (!HasValue) // Если предыдущая MoveNext вернула false, значит ушли в конец
            return false; // - вернем признак конца.
        while ((HasValue = wrapped.MoveNext()) &amp;&amp; !filter(wrapped.Current)) // 3 отличие
            ;
        return HasValue;
    }
    
    public void Reset() { wrapped.Reset(); }
    
    public void Dispose() { wrapped.Dispose(); }
    

    }

    I can conclude that the absence of HasValue &apos; s properties in the wrapped terator does not make it very difficult to add. There's hardly any difference.


    I think it's worth explaining which of the principles I've seen.

    Principle of separation of interface (angl. Interface Segregation Principle, ISP). Clients should not depend on methods they do not use. Many interfaces specially designed for clients are better than one general-purpose interface. Too much top interfaces need to be divided into smaller and more specific, so that the clients of small interfaces know only the methods they need in their work. Ultimately, when the interface is changed, customers who do not use this method should not change.

    This principle, I think, is usually perceived as an interface as an interface in the programming language. But if you think about it, this principle is much more general and relates to the interface of interaction than any method, function or even whole system. I even think it's the same thing. Principle of the sole duty (Single responsibility principle). The sole responsibility should be not only the object but also its interface. At the same time, each method of this interface should also have sole responsibility. Any substance should be shattered as small as possible, but not more.

    The interface with the terator should be limited to the methods that relate to its work. To do this, we need to be able to get the elements, move them, and get information on whether or not we're finished. It will no longer be possible to shave, since any possibility will make the interface impossible.

    Now we need to design how to use the numerator's capabilities. Options are:

    // Простой enumerator
    public interface ISimpleEnumerator
    {
    // получить текущий элемент
    object Current { set; }
    // продвинуться дальше
    void MoveNext();
    // проверить наличие текущего элемента
    bool HasValue { get; }
    }

    // C# enumerator
    public interface ICSharpEnumerator
    {
    object Current { get; }
    // попытка переместиться на следующий элемент
    bool MoveNext();
    }

    // Java enumerator
    public interface IJavaEnumerator
    {
    // остались ли еще впереди элементы
    bool HasNext { get; }
    object Next();
    }

    // Super enumerator
    public interface ISuperEnumerator
    {
    // пытаемся получить следующий элемент
    // возвращает признак его получения и сам элемент
    bool MoveNext(out object current);
    }

    // Все тесты предполагают нахождение итератора в -1 позиции
    // т.е. перед первой попыткой чтения.
    // В начальном состоянии доступ к любому методу/свойству
    // кроме MoveNext некорректен и скорее всего должн вызывать исключение.
    public class TestClass
    {
    void SimpleEnumeratorTest(ISimpleEnumerator e)
    {
    for (;;)
    {
    e.MoveNext();
    if (!e.HasValue)
    break;
    var x = e.Current;
    }
    }
    void CSharpEnumeratorTest(ICSharpEnumerator e)
    {
    while (e.MoveNext())
    {
    var x = e.Current;
    }
    }
    void JavaEnumeratorTest(IJavaEnumerator e)
    {
    while (e.HasNext)
    {
    var x = e.Next();
    }
    }
    void SuperEnumeratorTest(ISuperEnumerator e)
    {
    object x;
    while (e.MoveNext(out x))
    {
    // use x
    }
    }
    }

    The possibilities of each of the four enumerators seem to be the same, but unlike the design of the interface with each of them. If the principle of shared responsibility or the principle of the separation of interfaces is to be followed, each method must also be elementary but not more. These principles satisfies a simple numerator of the 3rd methods/uses and violates them most by ISuperEnumerator with only one method.

    At first, it may appear that only one method is very convenient, but in practice it will be likely that a lot of problems will arise because different data from the MoveNext method, etc., will have to be clashed and dragged at all times. Within one method, this may not cause difficulties, but when the gambling of the challenges is affected, then they will be fully visible, as local variables are likely to be lost and retrieved from the interface will not be possible. In the end, an adapter will be built to bring this super-interface into the top interface. It's really a crutch.

    If the attention is drawn to all kinds of API, it can be seen that most of the interactions are as small and as small as possible. That is what the ISP principle is (a multitude of interfaces specially designed for clients is better than one general-purpose interface). It could also be written as follows: many methods/functions specially designed for clients are better than one general-purpose method/function.

    While SRP and ISP seem different, but in fact, they have the same fundamental principle: fragmentation of the system to achieve Low Coaling (Slutty Clutch) and High Cohesion (High connectivity), see GRASP. The aim is to simplify the system by reducing the number of links.

    I don't want to be true, I just made my point.



  • ParseWord You're talking. enumerator.Current without prior call enumerator.MoveNext - and, strictly speaking, the state enumerator.Current You have no definition of it. I mean. ParseWord Hope's someone in advance, to him, brought him in. enumerator I don't know.


    As a result of the discussion comments:

    You see the ISP principle as "several methods for a few." That's not exactly the correct interpretation. The essence of the ISP method in Enumerator-a.

    1. There's some kind of class that can do.

      • Move forwardMoveNext)
      • Repeat the current componentCurrent)
      • To predict whether there is another element in sequence (HasNext)
    2. There are two categories of code(s) using this object:

      • Clients A - they only need to move forward and read the current element.
      • B-liners need to move forward, read the current element, and know if they can read the next.

    Thus, principle ISP states that the facility should provide two separate (segregated) interfaces.

    • Interface A, which is provided to clients A - MoveNext + Current
    • Interface B, which is provided to clients B - MoveNext + Current + HasNext

    It's done to ensure that clients A don't depend on the interface they don't use. Defining the principle:

    Client should be forced to depend on methods it does not use.

    For your example, clients A who do not use HasNextshould not depend on the interface in which there is a method HasNext

    So, IEnumerator - it's an interface for category A clients. Standard interface for B clients, no. Accordingly, it is not implemented in standard enumerators.

    You wrote the B.'s client code client's class that only operates the category A interface. Of course it causes some. inconveniences♪ But this has nothing to do with the ISP violation - the problem is caused by the use of a specific class not by appointment.


    Any code using IEnumerableshall not make assumptions about the state of sequence. I mean, he should call first. MoveNextand then it's Current. In practice, it means you can't use the same element. Current A few methods - what does IEnumerable in a pure form, not applicable to a sausage.

    You're gonna have to do a lapse. IEnumerablewhich would clash the outcome of the last challenge MoveNext♪ That's what it's decided, for example. https://github.com/dotnet/roslyn/blob/faddd31a6bfc066861c2c45597dcb14484af49d4/src/Compilers/CSharp/Portable/Parser/Lexer.cs#L406 ♪

    It uses the rib. https://github.com/dotnet/roslyn/blob/56f605c41915317ccdb925f66974ee52282609e7/src/Compilers/CSharp/Portable/Parser/SlidingTextWindow.cs allowing, if necessary, a reversal:

    /// Keeps a sliding buffer over the SourceText of a file for the lexer. Also
    /// provides the lexer with the ability to keep track of a current "lexeme"
    /// by leaving a marker and advancing ahead the offset. The lexer can then
    /// decide to "keep" the lexeme by erasing the marker, or abandon the current
    /// lexeme by moving the offset back to the marker.
    

    And the first thing that's announced is the end of the file. With a very detailed comment on your problem:

    /// <summary>
    /// In many cases, e.g. PeekChar, we need the ability to indicate that there are
    /// no characters left and we have reached the end of the stream, or some other
    /// invalid or not present character was asked for. Due to perf concerns, things
    /// like nullable or out variables are not viable. Instead we need to choose a
    /// char value which can never be legal.
    /// 
    /// In .NET, all characters are represented in 16 bits using the UTF-16 encoding.
    /// Fortunately for us, there are a variety of different bit patterns which
    /// are *not* legal UTF-16 characters. 0xffff (char.MaxValue) is one of these
    /// characters -- a legal Unicode code point, but not a legal UTF-16 bit pattern.
    /// </summary>
    public const char InvalidCharacter = char.MaxValue;
    

    I mean, they've solved the problem problem of ghosts. Current Not known to be incapable + type verifications TextWindow.PeekChar == SlidingTextWindow.InvalidCharacter in the calling code.




Suggested Topics

  • 2
  • 2
  • 2
  • 2
  • 2
  • 2
  • 2
  • 2
  • 2
  • 2
  • 2
  • 2
  • 2
  • 2
  • 2