c# - objetos - Usar un objeto IDisposable en el método que devuelve IEnumerable<T>
ejemplo de un constructor en c# (3)
Imagine que tiene un método que usa internamente un objeto IDisposable (por ejemplo, un lector de secuencias), y cede los elementos devueltos a medida que se leen del archivo. Me gusta esto:
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);
}
}
}
}
¿Se eliminarán el reader
y el filestream
cuando utilizo los métodos LINQ que no iteran en la colección completa?
YourOjbect firstLine = Read("myfile.txt").First();
Cuando utiliza el compilador de palabras clave de yield
genera clases anidadas, que implementa IEnumerable
, IEnumerator
e IDisposable
y almacena todos los datos de contexto:
[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; }
}
Como puede ver, todas las variables locales desde el contexto del método de rendimiento se movieron a los campos de esta clase generada. Los métodos interesantes son aquellos que tienen m_Finally
en sus nombres:
private void <>m__Finally4()
{
this.<>1__state = -1;
if (this.<filestream>5__1 != null)
{
this.<filestream>5__1.Dispose();
}
}
Como puede ver, estos métodos disponen de sus objetos desechables ( FileStream
y StreamReader
). Cuando el llamado? Al final de la enumeración, o cuando se llama Dispose
:
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 observa la implementación de Enumerable
de Fist()
, verá que llama a Dispose
después de devolver el primer elemento:
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext())
{
return enumerator.Current;
}
}
Por lo tanto, se llamará a Dispose
of auto-generated class, y todas las variables locales, que requieren eliminación, se eliminarán mediante llamadas a m_Finally
methods.
Por cierto (no sobre el uso con LINQ) si observas la implementación de la declaración foreach , verás que el enumerador se elimina después de la enumeración. Por lo tanto, se llamará a Dispose
en la clase generada incluso en caso de break
o excepción.
Este es un problema / pregunta general de LINQ, y sí, cuando se ejecuta, LINQ eliminará todos los elementos desechables que obtenga.
Sí, están dispuestos.
[Editar] Mientras utilice los métodos LINQ o los bucles foreach, la eliminación se realiza automáticamente. Sin embargo, si decides llamar manualmente a .Enumerator().MoveNext()
en el método, entonces debes encargarte de la eliminación. [/Editar]
Este código:
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");
}
Huellas dactilares:
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