39 votes

Utilisation d'un objet IDisposable dans une méthode qui renvoie IEnumerable <T>

Imaginez qu'une méthode utilise en interne un objet IDisposable (par exemple, un lecteur de flux), et renvoie les éléments renvoyés à mesure qu'ils sont lus à partir du fichier. Comme ça:

 public IEnumerable<YourObject> Read(string filename)
{
    using(var filestream = new FileStream(filename, FileMode.Open))
    {
        using(var reader = new StreamReader(filestream))
        {
            string line;

            while((line = reader.ReadLine()) != null)
            {
                yield return new YourObject(line);
            }
        }
    }
}
 

Les reader et les filestream seront-ils supprimés lorsque j'utilise des méthodes LINQ qui n'itèrent pas la collection complète?

 YourOjbect firstLine = Read("myfile.txt").First();
 

28voto

Sergey Berezovskiy Points 102044

Lorsque vous utilisez yield mot-clé le compilateur génère une classe imbriquée, qui implémente IEnumerable, IEnumerator et IDisposable et stocke toutes les données de contexte:

[CompilerGenerated]
private sealed class <Read>d__0 : IEnumerable<YourObject>, IEnumerable, IEnumerator<YourObject>, IEnumerator, IDisposable
{
    // Fields
    private int <>1__state;
    private YourObject <>2__current;
    public string <>3__filename;
    public Foo <>4__this;
    private int <>l__initialThreadId;
    public FileStream <filestream>5__1;
    public string <line>5__3;
    public StreamReader <reader>5__2;
    public string filename;

    // Methods
    [DebuggerHidden]
    public <Read>d__0(int <>1__state);
    private void <>m__Finally4();
    private void <>m__Finally5();
    private bool MoveNext();
    [DebuggerHidden]
    IEnumerator<YourObject> IEnumerable<YourObject>.GetEnumerator();
    [DebuggerHidden]
    IEnumerator IEnumerable.GetEnumerator();
    [DebuggerHidden]
    void IEnumerator.Reset();
    void IDisposable.Dispose();

    // Properties
    YourObject IEnumerator<YourObject>.Current { [DebuggerHidden] get; }
    object IEnumerator.Current { [DebuggerHidden] get; }
}

Comme vous pouvez le voir, toutes les variables locales de contexte de céder méthode déplacé vers les champs de cette classe générée. Méthodes intéressantes sont celles qui ont m_Finally de leurs noms:

private void <>m__Finally4()
{
    this.<>1__state = -1;
    if (this.<filestream>5__1 != null)
    {
        this.<filestream>5__1.Dispose();
    }
}

Comme vous pouvez le voir, ces méthodes de disposer vos objets jetables (FileStream et StreamReader). Lorsque l'appelé? À la fin de l'énumération, ou lors de l' Dispose s'appelle:

private bool MoveNext()
{
    bool CS$1$0000;
    try
    {
        int CS$4$0001 = this.<>1__state;
        if (CS$4$0001 != 0)
        {
            if (CS$4$0001 != 3)
            {
                goto Label_00AB;
            }
            goto Label_0074;
        }
        this.<>1__state = -1;
        this.<filestream>5__1 = new FileStream(this.filename, FileMode.Open);
        this.<>1__state = 1;
        this.<reader>5__2 = new StreamReader(this.<filestream>5__1);
        this.<>1__state = 2;
        while ((this.<line>5__3 = this.<reader>5__2.ReadLine()) != null)
        {
            this.<>2__current = new YourObject(this.<line>5__3);
            this.<>1__state = 3;
            return true;
        Label_0074:
            this.<>1__state = 2;
        }
        this.<>m__Finally5();
        this.<>m__Finally4();
    Label_00AB:
        CS$1$0000 = false;
    }
    fault
    {
        this.System.IDisposable.Dispose();
    }
    return CS$1$0000;
}

void IDisposable.Dispose()
{
    switch (this.<>1__state)
    {
        case 1:
        case 2:
        case 3:
            try
            {
                switch (this.<>1__state)
                {
                    case 2:
                    case 3:
                        break;

                    default:
                        break;
                }
                try
                {
                }
                finally
                {
                    this.<>m__Finally5();
                }
            }
            finally
            {
                this.<>m__Finally4();
            }
            break;
    }
}

Si vous regardez à l' Fist() de la mise en œuvre de l' Enumerable,, alors vous allez le voir - il appelle Dispose après le retour du premier élément:

using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
   if (enumerator.MoveNext())
   {
       return enumerator.Current;
   }
}

Ainsi, Dispose des auto-généré classe sera appelée, et toutes les variables locales, qui exigent l'élimination seront éliminés par des appels d' m_Finally méthodes.

BTW (et non sur l'utilisation de LINQ) si vous regardez l' instruction foreach mise en œuvre , vous verrez que l'agent recenseur est disposé après l'énumération. Ainsi, Dispose sur la classe générée sera appelé même en cas d' break ou d'exception.

17voto

Tim S. Points 30377

Oui, ils sont disposés.

[Modifier] Tant que vous utilisez des méthodes LINQ ou des boucles foreach, la mise au rebut est automatiquement prise en charge. Cependant, si vous décidez d'appeler manuellement .Enumerator().MoveNext() sur la méthode, vous devez alors vous en occuper vous-même. [/Modifier]

Ce code:

 class something : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Disposing");
        Console.WriteLine(Environment.StackTrace);
    }
}
static IEnumerable<string> ie()
{
    using (new something())
    {
        Console.WriteLine("first");
        yield return "first";
        Console.WriteLine("second");
        yield return "second";
    }
}
static void Main(string[] args)
{
    Console.WriteLine("before");
    ie().First();
    Console.WriteLine("after");
}
 

Impressions:

 before
first
Disposing
   at System.Environment.GetStackTrace(Exception e, Boolean needFileInfo)
   at System.Environment.get_StackTrace()
   at TestApp.Program.something.Dispose() in C:\Users\Tim\Documents\Visual Studi
o 2010\Projects\TestApp\TestApp\Program.cs:line 198
   at TestApp.Program.<ie>d__0.<>m__Finally2() in C:\Users\Tim\Documents\Visual
Studio 2010\Projects\TestApp\TestApp\Program.cs:line 0
   at TestApp.Program.<ie>d__0.System.IDisposable.Dispose() in C:\Users\Tim\Docu
ments\Visual Studio 2010\Projects\TestApp\TestApp\Program.cs:line 0
   at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
   at TestApp.Program.Main(String[] args) in C:\Users\Tim\Documents\Visual Studi
o 2010\Projects\TestApp\TestApp\Program.cs:line 214
   at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args
)
   at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySec
urity, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, C
ontextCallback callback, Object state, Boolean ignoreSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, C
ontextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()
after
 

1voto

TomTom Points 35574

Ceci est un problème / une question générale de LINQ, et oui - LINQ éliminera tous les éléments jetables une fois exécutés.

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