programowanieProgramista bibliotek .NET / Średni programista VB.NET

Opisz sposoby realizacji i zastosowania iteratorów (termin Enumerable / Iterator) w Visual Basic, a także jak tworzyć własne iterowane kolekcje.

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

Historia pytania
Iteratory stały się ważną częścią języków programowania z pojawieniem się kolekcji, które trzeba przeszukiwać. W Visual Basic od wersji .NET pojawiło się wsparcie dla IEnumerable/IEnumerator, umożliwiające implementację własnych iterowanych kolekcji i wykorzystanie pętli For Each do wygodnego i zwięzłego dostępu do zawartości kolekcji.

Problem
Wielu programistów ogranicza się do korzystania tylko z wbudowanych kolekcji lub niewłaściwie zaimplementowuje interfejs IEnumerable, przez co niemożliwe jest przeszukiwanie obiektów za pomocą For Each. Ponadto, problemy sprawia implementacja i utrzymanie stanu iteratora przez Current i MoveNext.

Rozwiązanie
Aby wspierać iteracje, typ implementuje interfejs IEnumerable (lub generyczny IEnumerable(Of T)). Zaimplementuj funkcję GetEnumerator, która zwraca obiekt implementujący IEnumerator. W VB.NET można też stosować metody iteratorowe z kluczowym słowem Yield (od VB 2015), co znacznie upraszcza realizację przeszukiwania.

Przykład kodu:

Imports System.Collections Public Class SimpleCollection Implements IEnumerable Private arr() As Integer = {1, 2, 3} Public Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator For Each i In arr Yield i ' dla VB 2015+ Next End Function End Class ' Użycie: For Each n As Integer In New SimpleCollection() Console.WriteLine(n) Next

Kluczowe cechy:

  • Aby wspierać For Each, typ musi implementować IEnumerable / IEnumerable(Of T).
  • Implementacja zwykle delegowana jest do istniejącej kolekcji lub tworzona jest własna.
  • Z pomocą Yield można łatwo tworzyć iteratory; bez Yield — trzeba ręcznie implementować IEnumerator.

Pytania z podstępem.

Czy konieczne jest zaimplementowanie obu interfejsów: IEnumerable i IEnumerator, aby wspierać For Each?

Nie, wystarczy implementacja IEnumerable i zwrócenie zgodnego iteratora. Jeśli chcesz zaimplementować przeszukiwanie, możesz użyć już istniejącego IEnumerator z zagnieżdżonej kolekcji. Ale dla pełnej personalizacji konieczna jest implementacja obu części.

Przykład kodu:

Public Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator Return arr.GetEnumerator() ' używamy wbudowanego iteratora tablicy End Function

Czy można zaimplementować IEnumerable tylko jawnie (Explicit Interface Implementation) w Visual Basic?

Tak, można zaimplementować interfejs jawnie, szczególnie gdy Twoja kolekcja już ma własny GetEnumerator i chcesz ukryć implementację interfejsu. Nie uda się wywołać takiego GetEnumerator bezpośrednio, ale For Each będzie działać.

Przykład kodu:

Public Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator ' ...ciało metody End Function

Co się stanie, jeśli GetEnumerator zwróci Nothing?

Zostanie zgłoszone wyjątek w momencie pierwszej próby przeszukiwania kolekcji, ponieważ For Each oczekuje poprawnego iteratora.

Typowe błędy i antywzorce

  • Niewłaściwa implementacja metod MoveNext/Current, przez co pomijane są elementy lub występuje błąd.
  • Naruszenie stanu iteratora między wywołaniami (np. nie resetuje indeksu przy ponownym przeszukiwaniu).
  • Brakujące lub niepoprawne zgłoszenie wyjątku StopIteration.

Przykład z życia

Negatywny przypadek

Klasa kolekcji nie zaimplementowała IEnumerable, a osobna metoda Next została utworzona do przeszukiwania elementów. W efekcie nie można jej używać w uniwersalnych metodach lub For Each, trzeba pisać dodatkowe metody.

Zalety:

  • Szybka implementacja pod zadanie.

Wady:

  • Nie można używać For Each i LINQ.
  • Utrata kompatybilności z .NET API.

Pozytywny przypadek

Programista zaimplementował IEnumerable i GetEnumerator, kolekcja teraz działa z For Each i integruje się z LINQ.

Zalety:

  • Superkompatybilność z infrastrukturą .NET.
  • Uniwersalność i prostota użycia.

Wady:

  • Wymaga nieco więcej kodu przy ręcznej implementacji przed pojawieniem się Yield.