2011-11-03 09:34:28 UTC

Как известно, using в C# это try/finaly с вызовом Dispose() в блоке finally, но компилятор для этой конструкции генерирует не самый оптимальный код для подавляющего большинства случаев.

Имеем использование объекта IDisposable, с using и без него:

static void Using()
{
	using (MemoryStream ms = new MemoryStream())
	{
		ms.Flush();
	}
}

static void NoUsing()
{
	MemoryStream ms = new MemoryStream();
	try
	{
		ms.Flush();
	}
	finally
	{
		ms.Dispose();
	}
}

Код для обоих генерируется одинаковый, за исключением одного ньюанса.

Код с using:

.method private hidebysig static void Using() cil managed
{
    .maxstack 1
    .locals init (
        [0] class [mscorlib]System.IO.MemoryStream ms)
    L_0000: newobj instance void [mscorlib]System.IO.MemoryStream::.ctor()
    L_0005: stloc.0 
    L_0006: ldloc.0 
    L_0007: callvirt instance void [mscorlib]System.IO.Stream::Flush()
    L_000c: leave.s L_0018
    L_000e: ldloc.0 
    L_000f: brfalse.s L_0017
    L_0011: ldloc.0 
    L_0012: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    L_0017: endfinally 
    L_0018: ret 
    .try L_0006 to L_000e finally handler L_000e to L_0018
}

И «самописный» using:

.method private hidebysig static void NoUsing() cil managed
{
    .maxstack 1
    .locals init (
        [0] class [mscorlib]System.IO.MemoryStream ms)
    L_0000: newobj instance void [mscorlib]System.IO.MemoryStream::.ctor()
    L_0005: stloc.0 
    L_0006: ldloc.0 
    L_0007: callvirt instance void [mscorlib]System.IO.Stream::Flush()
    L_000c: leave.s L_0015
    L_000e: ldloc.0 
    L_000f: callvirt instance void [mscorlib]System.IO.Stream::Dispose()
    L_0014: endfinally 
    L_0015: ret 
    .try L_0006 to L_000e finally handler L_000e to L_0015
}

Обратите внимание на инструкцию brfalse.s L_0017 в блоке finally для первого варианта, — по сути она лишняя, т.к. инициализация объекта находится за пределами try и если инициализация будет неуспешной, то будет выброшено исключение. Если же объект проинициализируется, то он гарантированно не будет null, поэтому проверка объекта на null по сути лишняя.

Если же переписать наш «самописный» using c проверкой на null в finally, будет сгенерирован аналогичный код, как при использовании using:

static void NoUsing()
{
	MemoryStream ms = new MemoryStream();
	try
	{
		ms.Flush();
	}
	finally
	{
		if ( ms != null )
		{
			ms.Dispose();
		}
	}
}

Результат:

.method private hidebysig static void NoUsing() cil managed
{
    .maxstack 1
    .locals init (
        [0] class [mscorlib]System.IO.MemoryStream ms)
    L_0000: newobj instance void [mscorlib]System.IO.MemoryStream::.ctor()
    L_0005: stloc.0 
    L_0006: ldloc.0 
    L_0007: callvirt instance void [mscorlib]System.IO.Stream::Flush()
    L_000c: leave.s L_0018
    L_000e: ldloc.0 
    L_000f: brfalse.s L_0017
    L_0011: ldloc.0 
    L_0012: callvirt instance void [mscorlib]System.IO.Stream::Dispose()
    L_0017: endfinally 
    L_0018: ret 
    .try L_0006 to L_000e finally handler L_000e to L_0018
}
2011-11-03 09:34:28 UTC csharp ilasm microsoft noweb performance programming