Bir denemede gerçekten ne olur {return x; } sonunda {x = null; } Beyan?


259

Bu ipucunu başka bir soruda gördüm ve birisinin bana bunun nasıl işlediğini açıklayabileceğini merak ediyordum?

try { return x; } finally { x = null; }

Ben, ne anlama gelir finallyfıkra gerçekten yürütmek sonrareturn açıklamada? Bu kod ne kadar güvenli değil? Bu try-finallyhack olmadan yapılabilecek herhangi bir ek hackery düşünebilir misiniz ?

Yanıtlar:


235

Hayır - IL düzeyinde, kural dışı durum içeren bir bloğun içinden geri dönemezsiniz. Temelde bir değişkende saklar ve daha sonra geri döner

yani şuna benzer:

int tmp;
try {
  tmp = ...
} finally {
  ...
}
return tmp;

örneğin (reflektör kullanarak):

static int Test() {
    try {
        return SomeNumber();
    } finally {
        Foo();
    }
}

derler:

.method private hidebysig static int32 Test() cil managed
{
    .maxstack 1
    .locals init (
        [0] int32 CS$1$0000)
    L_0000: call int32 Program::SomeNumber()
    L_0005: stloc.0 
    L_0006: leave.s L_000e
    L_0008: call void Program::Foo()
    L_000d: endfinally 
    L_000e: ldloc.0 
    L_000f: ret 
    .try L_0000 to L_0008 finally handler L_0008 to L_000e
}

Bu temelde bir yerel değişkeni ( CS$1$0000) bildirir , değeri değişkene (işlenen bloğun içine) yerleştirir, sonra bloktan çıktıktan sonra değişkeni yükler, sonra döndürür. Reflektör bunu şu şekilde yapar:

private static int Test()
{
    int CS$1$0000;
    try
    {
        CS$1$0000 = SomeNumber();
    }
    finally
    {
        Foo();
    }
    return CS$1$0000;
}

10
Bu tam olarak ocdedio'nun söylediği şey değil mi: nihayet dönüş değerinin hesaplanmasından sonra ve gerçekten işlevden dönmeden önce yürütülür ???
mmmmmmmm

"istisna işleme blok" Bence bu senaryonun istisnalar ve istisna işleme ile ilgisi yoktur. Bu, .NET'in son olarak kaynak koruma yapısını nasıl uyguladığı ile ilgilidir .
g.pickardou

361

Nihayet deyimi yürütülür, ancak dönüş değeri etkilenmez. İcra emri:

  1. Dönüş ifadesi yürütülmeden önceki kod
  2. Dönüş ifadesindeki ifade değerlendirilir
  3. sonunda blok yürütülür
  4. 2. adımda değerlendirilen sonuç döndürülür

İşte size gösterilecek kısa bir program:

using System;

class Test
{
    static string x;

    static void Main()
    {
        Console.WriteLine(Method());
        Console.WriteLine(x);
    }

    static string Method()
    {
        try
        {
            x = "try";
            return x;
        }
        finally
        {
            x = "finally";
        }
    }
}

Bu "try" (çünkü döndürülen şey) ve sonra "nihayet" yazdırır, çünkü x'in yeni değeri budur.

Tabii ki, değişebilir bir nesneye (örneğin bir StringBuilder) bir referans döndürüyorsak, sonunda blokta nesnede yapılan herhangi bir değişiklik dönüşte görünür olacaktır - bu dönüş değerini etkilememiştir (bu sadece bir referans).


Ben sormak istiyorum, görsel stüdyoda icra sırasında yazılı C # kodu için oluşturulan Ara dili (IL) görmek için herhangi bir seçenek var mı ....
Enigma State

"Değişken bir nesneye (örneğin bir StringBuilder) bir başvuru döndürüyorsak, son blokta nesnede yapılan değişiklikler dönüşte görünür olacak" istisnası, StringBuilder nesnesi sonunda blokta null değerine ayarlanırsa, bu durumda null olmayan bir nesne döndürülür.
Nick

4
@ Nick: Yani bir değişiklik değil nesne - bir değişikliktir değişken . Değişkenin önceki değerinin hiç değinmediği nesneyi etkilemez. Yani hayır, bu bir istisna değil.
Jon Skeet

3
@Skeet Bu, "return ifadesi bir kopya döndürür" anlamına mı geliyor?
prabhakaran

4
@prabhakaran: İfadenin yapıldığı noktada ifadeyi değerlendirir returnve bu değer döndürülür. İfadesidir olmayan kontrol yaprakları gibi yöntem değerlendirildi.
Jon Skeet

19

Nihayet cümlesi, return deyiminden sonra ancak işlevden geri dönmeden önce yürütülür. Sanırım iplik güvenliği ile pek ilgisi yok. Bu bir hack değil - nihayet denemenizde veya catch bloğunuzda ne yaparsanız yapın her zaman çalışacağı garanti edilir.


13

Marc Gravell ve Jon Skeet tarafından verilen cevaplara ek olarak, nesnelerin ve diğer referans türlerinin iade edildiğinde benzer şekilde davrandıklarını, ancak bazı farklılıkların olduğunu not etmek önemlidir.

Döndürülen "Ne" ifadesi, basit türlerle aynı mantığı izler:

class Test {
    public static Exception AnException() {
        Exception ex = new Exception("Me");
        try {
            return ex;
        } finally {
            // Reference unchanged, Local variable changed
            ex = new Exception("Not Me");
        }
    }
}

Yerel değişkene son blokta yeni bir referans atanmadan önce döndürülen referans zaten değerlendirilmiştir.

Yürütme esasen:

class Test {
    public static Exception AnException() {
        Exception ex = new Exception("Me");
        Exception CS$1$0000 = null;
        try {
            CS$1$0000 = ex;
        } finally {
            // Reference unchanged, Local variable changed
            ex = new Exception("Not Me");
        }
        return CS$1$0000;
    }
}

Fark, dikkatli olmamanız durumunda beklenmedik davranışlara neden olabilecek nesnenin özelliklerini / yöntemlerini kullanarak değiştirilebilir türleri değiştirmenin mümkün olabilmesidir.

class Test2 {
    public static System.IO.MemoryStream BadStream(byte[] buffer) {
        System.IO.MemoryStream ms = new System.IO.MemoryStream(buffer);
        try {
            return ms;
        } finally {
            // Reference unchanged, Referenced Object changed
            ms.Dispose();
        }
    }
}

Try-return-nihayetinde dikkate alınması gereken ikinci bir şey de, "referans ile" geçirilen parametrelerin, dönüşten sonra yine de değiştirilebilmesidir. Yalnızca dönüş değeri değerlendirildi ve döndürülmeyi bekleyen geçici bir değişkente saklandı, diğer değişkenler yine de normal şekilde değiştirildi. Bir out parametresinin sözleşmesi, nihayet bu şekilde engellenene kadar yerine getirilemez.

class ByRefTests {
    public static int One(out int i) {
        try {
            i = 1;
            return i;
        } finally {
            // Return value unchanged, Store new value referenced variable
            i = 1000;
        }
    }

    public static int Two(ref int i) {
        try {
            i = 2;
            return i;
        } finally {
            // Return value unchanged, Store new value referenced variable
            i = 2000;
        }
    }

    public static int Three(out int i) {
        try {
            return 3;
        } finally {
            // This is not a compile error!
            // Return value unchanged, Store new value referenced variable
            i = 3000;
        }
    }
}

Diğer herhangi bir akış yapısı gibi "try-return-nihayet" yerine sahiptir ve gerçekte derlediği yapıyı yazmaktan daha temiz görünümlü kodlara izin verebilir . Ancak gotcha'lardan kaçınmak için dikkatle kullanılmalıdır.


4

Eğer xyerel bir değişkendir, ben, nokta görmüyorum xetkili bir yöntem çıkıldığında zaten null olarak ayarlanmış ve sete aramadan önce kayıttaki yerleştirildi beri dönüş değeri değeri (boş değil edilecektir xnull).

Sadece dönüşte (ve dönüş değeri belirlendikten sonra) bir alanın değerinin değişimini garanti etmek istiyorsanız bunu yapmayı görebiliyorum.


Yerel değişken bir delege tarafından yakalanmadığı sürece :)
Jon Skeet

Sonra bir kapanış var ve nesne hala toplanamıyor, çünkü hala bir referans var.
özyinelemeli

Ama yine de değerini kullanmayı düşünmedikçe bir delegede neden yerel bir değişken kullanacağınızı anlamıyorum.
özyinelemeli
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.