Perché IEnumerator < T > ereditare da IDisposable mentre l'IEnumerator non generico non lo fa?

StackOverflow https://stackoverflow.com/questions/232558

  •  04-07-2019
  •  | 
  •  

Domanda

Ho notato che il generico IEnumerator < T > eredita da IDisposable, ma l'interfaccia non generica IEnumerator no. Perché è progettato in questo modo?

Di solito, usiamo foreach statement per passare attraverso un'istanza IEnumerator < T > . Il codice generato di foreach in realtà ha un blocco try-finally che richiama Dispose () alla fine.

È stato utile?

Soluzione

Fondamentalmente è stata una svista. In C # 1.0, foreach mai chiamato Dispose 1 . Con C # 1.2 (introdotto in VS2003 - non c'è 1.1, in modo bizzarro) foreach ha iniziato a controllare nel blocco finalmente se l'iteratore implementava o meno IDisposable - dovevano farlo in quel modo, perché rendere retrospettivamente IEnumerator estendere IDisposable avrebbe compromesso l'implementazione di IEnumerator da parte di tutti. Se avessero scoperto che è utile in primo luogo foreach eliminare gli iteratori, sono sicuro che IEnumerator avrebbe esteso IDisposable .

Quando sono usciti C # 2.0 e .NET 2.0, hanno avuto una nuova opportunità: nuova interfaccia, nuova eredità. È molto più sensato che l'interfaccia estenda IDisposable in modo che non sia necessario un controllo del tempo di esecuzione nel blocco finally, e ora il compilatore sa che se l'iteratore è un IEnumerator < ; T > può emettere una chiamata incondizionata a Dispose .

EDIT: è incredibilmente utile che Dispose sia chiamato alla fine dell'iterazione (comunque finisca). Significa che l'iteratore può trattenere le risorse, il che rende possibile, per esempio, leggere un file riga per riga. I blocchi di Iteratore generano implementazioni Dispose che assicurano che eventuali blocchi infine rilevanti per il "punto corrente di esecuzione" dell'iteratore viene eseguito quando viene eliminato, quindi è possibile scrivere il normale codice all'interno dell'iteratore e la pulizia dovrebbe avvenire in modo appropriato.


1 Guardando indietro alla specifica 1.0, era già stata specificata. Non sono ancora stato in grado di verificare questa affermazione precedente che l'implementazione 1.0 non ha chiamato Dispose .

Altri suggerimenti

IEnumerable < T > non eredita IDisposable. IEnumerator < T > eredita tuttavia IDisposable, mentre l'IEnumerator non generico non lo fa. Anche quando si utilizza foreach per un IEnumerable non generico (che restituisce IEnumerator), il compilatore genererà comunque un controllo per IDisposable e chiamerà Dispose () se l'enumeratore implementa l'interfaccia.

Suppongo che l'enumeratore generico < T > eredita da IDisposable, quindi non è necessario un controllo del tipo di runtime: può semplicemente andare avanti e chiamare Dispose () che dovrebbe avere prestazioni migliori poiché può essere probabilmente ottimizzato se l'enumeratore ha un metodo Dispose () vuoto .

So che questa è una vecchia discussione, ma ho sicuramente scritto una libreria in cui ho usato IEnumerable di T / IEnumerator di T in cui gli utenti della libreria potevano implementare iteratori personalizzati che dovevano implementare IEnumerator di T.

Ho trovato molto strano che IEnumerator di T ereditasse da IDisposable. Implementiamo IDisposable se vogliamo liberare risorse non modificate, giusto? Quindi sarebbe rilevante solo per gli enumeratori che in realtà contengono risorse non gestite - come un flusso IO ecc. Perché non consentire agli utenti di implementare sia IEnumerator di T sia IDisposable sul loro enumeratore se ha senso? Nel mio libro questo viola il principio della singola responsabilità - Perché mescolare la logica dell'enumeratore e lo smaltimento degli oggetti.

IEnumerable` eredita IDisposing? Secondo il riflettore .NET o MSDN . Sei sicuro di non confonderlo con IEnumerator ? Questo utilizza IDisposing perché solo per enumerare una raccolta e non per longevità.

Un po 'difficile essere definitivi su questo, a meno che tu non riesca a ottenere una risposta dallo stesso AndersH o da qualcuno vicino a lui.

Tuttavia, la mia ipotesi è che si riferisce al "rendimento" parola chiave introdotta contemporaneamente in C #. Se osservi il codice generato dal compilatore quando " yield return x " viene utilizzato, vedrai il metodo racchiuso in una classe di supporto che implementa IEnumerator; avere IEnumerator discendente da IDisposable garantisce che possa essere pulito una volta completata l'enumerazione.

IIRC L'intera questione di avere IEnumerable < T > e IEnumerable è il risultato di IEnumerable che precede il modello di .Net. Ho il sospetto che la tua domanda sia allo stesso modo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top