27 votes

IEnumerable.Take (0) sur File.ReadLines ne semble pas disposer / fermer le descripteur de fichier

J'ai une fonction qui Saute n des lignes de code et Prend y des lignes à partir d'un fichier à l'aide d' File.ReadLines avec Skip et Take combinaison. Lorsque j'essaie d'ouvrir le fichier donné en filePath la prochaine fois:

string[] Lines = File.ReadLines(filePath).Skip(0).Take(0).ToArray();
using (StreamWriter streamWriter = new StreamWriter(filePath))
{
    // ...
}

Je reçois File in use by another process d'exception sur le "using" de ligne.

Il ressemble IEnumerable.Take(0) est le coupable, car il renvoie un vide IEnumerable sans l'énumération de l'objet retourné par File.ReadLines(), ce qui je crois n'est pas de disposer du fichier.

Suis-je le droit? Ne devraient-ils pas énumérer pour éviter ce genre d'erreurs? Comment le faire correctement?

39voto

Jon Skeet Points 692016

C'est un bug en File.ReadLines, pas Take. ReadLines renvoie un IEnumerable<T>, qui devrait logiquement être paresseux, mais il avec impatience ouvre le fichier. Sauf si vous avez réellement faire une itération sur la valeur de retour, vous n'avez rien à jeter.

Il est également cassé en termes de la seule itération d'une fois. Par exemple, vous devriez être en mesure d'écrire:

var lines = File.ReadLines("text.txt");
var query = from line1 in lines
            from line2 in lines
            select line1 + line2;

... qui devrait donner un produit croisé de lignes dans le fichier. Ce n'est pas, en raison de la brisure.

File.ReadLines devrait être mis en place quelque chose comme ceci:

public static IEnumerable<string> ReadLines(string filename)
{
    return ReadLines(() => File.OpenText(filename));
}

private static IEnumerable<string> ReadLines(Func<TextReader> readerProvider)
{
    using (var reader = readerProvider())
    {
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            yield return line;
        }
    }
}

Malheureusement, il n'est pas :(

Options:

  • Utilisez la dessus au lieu de File.ReadLines
  • Écrivez votre propre mise en œuvre de l' Take qui toujours commence itération, par exemple

    public static IEnumerable<T> Take<T>(this IEnumerable<T> source, int count)
    {
        // TODO: Argument validation
        using (var iterator = source.GetEnumerator())
        {
            while (count > 0 && iterator.MoveNext())
            {
                count--;
                yield return iterator.Current;
            }
        }
    }
    

18voto

CodeCaster Points 38181

Dans le commentaire ci-dessus File.ReadLines() dans la Source de Référence, il devient évident que l'équipe responsable savais à propos de ce "bug":

Problèmes connus qui ne peut pas être changé pour rester compatible avec les 4.0:

  • Le sous-jacent StreamReader est attribué à l'avance pour l' IEnumerable<T> avant GetEnumerator a même été appelé. Tout ce qui est bon dans le fait que les exceptions telles que DirectoryNotFoundException et FileNotFoundException sont jetés directement par File.ReadLines (sur laquelle l'utilisateur a probablement s'attend), cela signifie aussi que le lecteur sera coulé si l'utilisateur n'a en fait jamais foreach sur le énumérable (et donc appelle dispose au moins d'un IEnumerator<T> exemple).

Donc, ils voulaient File.ReadLines() de jeter immédiatement lorsqu'il est passé d'un invalide ou illisible chemin, par opposition à lancer lors de l'énumération.

L'alternative est simple: pas d'appel Take(0), ou plutôt de ne pas lire le fichier tout à fait si vous n'êtes pas réellement intéressé par son contenu.

-1voto

stop-cran Points 2461

À mon avis, la cause racine est Enumerable.Take itérateur ne dispose pas d'un itérateur sous-jacent si le count est zéro, car le code n'entre pas le foreach boucle - voir référence . Si l'on modifie le code de la manière suivante, le problème est résolu:

 static IEnumerable<TSource> TakeIterator<TSource>(IEnumerable<TSource> source, int count)
{
    foreach (TSource element in source)
    {
        if (--count < 0) break;
        yield return element;
    }
}
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X