Я обычно делаю это, потому что думаю, что тогда область памяти выделяется для этого StreamReader только один раз.
Это то, что вы получаете неправильно.
Определенный объем стека занимает локальная переменная на время ее использования, а также определенный объем кучи занимает объект после new
. Это не изменится.
Действительно, компилятор в конечном итоге занимает немного больше места в стеке с вашим подходом. Просто сравните IL двух методов, использующих каждый подход. Мы будем использовать этот С#:
private static string LastLine1(NetworkStream networkStream)
{
string last = null;
StreamReader reader;
while(!ShouldStop)
{
using(reader = new StreamReader(networkStream))
{
string line = reader.ReadLine();
if(line != null)
last = line;
}
}
return last;
}
private static string LastLine2(NetworkStream networkStream)
{
string last = null;
while(!ShouldStop)
{
using(StreamReader reader = new StreamReader(networkStream))
{
string line = reader.ReadLine();
if(line != null)
last = line;
}
}
return last;
}
И мы получаем этот CIL:
.method private hidebysig static
string LastLine1 (
class [System]System.Net.Sockets.NetworkStream networkStream
) cil managed
{
.maxstack 2
.locals init (
[0] string,
[1] class [mscorlib]System.IO.StreamReader,
[2] string,
[3] class [mscorlib]System.IO.StreamReader
)
IL_0000: ldnull
IL_0001: stloc.0
IL_0002: br.s IL_0025
IL_0004: ldarg.0
IL_0005: newobj instance void [mscorlib]System.IO.StreamReader::.ctor(class [mscorlib]System.IO.Stream)
IL_000a: dup
IL_000b: stloc.1
IL_000c: stloc.3
.try
{
IL_000d: ldloc.1
IL_000e: callvirt instance string [mscorlib]System.IO.TextReader::ReadLine()
IL_0013: stloc.2
IL_0014: ldloc.2
IL_0015: brfalse.s IL_0019
IL_0017: ldloc.2
IL_0018: stloc.0
IL_0019: leave.s IL_0025
}
finally
{
IL_001b: ldloc.3
IL_001c: brfalse.s IL_0024
IL_001e: ldloc.3
IL_001f: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0024: endfinally
}
IL_0025: call bool Demonstrate.Program::get_ShouldStop()
IL_002a: brfalse.s IL_0004
IL_002c: ldloc.0
IL_002d: ret
}
.method private hidebysig static
string LastLine2 (
class [System]System.Net.Sockets.NetworkStream networkStream
) cil managed
{
.maxstack 1
.locals init (
[0] string,
[1] class [mscorlib]System.IO.StreamReader,
[2] string
)
IL_0000: ldnull
IL_0001: stloc.0
IL_0002: br.s IL_0023
IL_0004: ldarg.0
IL_0005: newobj instance void [mscorlib]System.IO.StreamReader::.ctor(class [mscorlib]System.IO.Stream)
IL_000a: stloc.1
.try
{
IL_000b: ldloc.1
IL_000c: callvirt instance string [mscorlib]System.IO.TextReader::ReadLine()
IL_0011: stloc.2
IL_0012: ldloc.2
IL_0013: brfalse.s IL_0017
IL_0015: ldloc.2
IL_0016: stloc.0
IL_0017: leave.s IL_0023
}
finally
{
IL_0019: ldloc.1
IL_001a: brfalse.s IL_0022
IL_001c: ldloc.1
IL_001d: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0022: endfinally
}
IL_0023: call bool Demonstrate.Program::get_ShouldStop()
IL_0028: brfalse.s IL_0004
IL_002a: ldloc.0
IL_002b: ret
}
(Строго говоря, эти два действительно должны были привести к идентичному коду, но на самом деле это не так, и ваш подход заключался в немного более длинном и немного большем использовании пространства стека).
Поскольку компилятор C# не смог оптимизировать использование reader
за пределами использования, на самом деле именно ваш подход приводит к тому, что дополнительное пространство стека занимает другая копия reader
.
Если вы не знакомы с CIL, сравните, как ILSpy пытается снова декомпилировать их обратно в C#:
private static string LastLine1(NetworkStream networkStream)
{
string result = null;
while (!Program.ShouldStop)
{
StreamReader streamReader2;
StreamReader streamReader = streamReader2 = new StreamReader(networkStream);
try
{
string text = streamReader.ReadLine();
if (text != null)
{
result = text;
}
}
finally
{
if (streamReader2 != null)
{
((IDisposable)streamReader2).Dispose();
}
}
}
return result;
}
private static string LastLine2(NetworkStream networkStream)
{
string result = null;
while (!Program.ShouldStop)
{
using (StreamReader streamReader = new StreamReader(networkStream))
{
string text = streamReader.ReadLine();
if (text != null)
{
result = text;
}
}
}
return result;
}
(Возможно также, что вы уменьшили вероятность того, что проверка на нулевое значение будет оптимизирована, когда она затем будет преобразована в машинный код, который фактически запускается).
это гораздо менее эффективно и производительно. Однако я не знаю, регулярно ли первое использование оператора using вызывает функцию Dispose() экземпляра.
using
вызывает Dispose()
нормально в любом случае, однако вы немного более расточительны и, следовательно, немного менее эффективны и производительны. Вероятно, незначительно, но подход, которого вы избегаете, определенно не «гораздо менее эффективен и эффективен», как вы утверждаете.
В общем, держите свои прицелы в напряжении. Основная причина заключается в том, что переменная, которая больше не находится в области видимости, — это переменная, с которой вы больше не можете сделать что-то не так или даже не должны думать о ней, поэтому у вас будет более чистый код с меньшим количеством ошибок и там, где ошибки будут легче найти. Вторая причина заключается в том, что в некоторых случаях, подобных этому, более широкая область действия приводит к чуть более расточительному коду.
Теперь размещение назначений вне цикла действительно может быть более эффективным. Если ваш код может работать с:
using(var reader = new StreamReader(networkStream))
while(!ShouldStop)
{
// do stuff
}
Тогда это уменьшит отток кучи и, прежде всего, делает меньше, и, следовательно, если это сработает, это будет улучшением.
Объявления, однако, ничего не делают, поэтому их наличие вне циклов не помогает, а иногда и немного мешает.
person
Jon Hanna
schedule
01.05.2015