Pertanyaan Tentukan apakah mengeksekusi pada akhirnya memblokir karena pengecualian yang dilemparkan


Apakah mungkin untuk menentukan apakah kode saat ini sedang dieksekusi dalam konteks a finally handler sebagai akibat dari pengecualian yang dilemparkan? Saya agak suka menggunakan IDisposable pola untuk mengimplementasikan fungsi pelolosan masuk / keluar, tetapi satu kekhawatiran dengan pola ini adalah bahwa Anda mungkin tidak ingin perilaku end-of-scope terjadi jika pengecualian terjadi di badan using. Saya akan mencari sesuatu seperti ini:

public static class MyClass
{
    public static void MyMethod()
    {
        using (var scope = MyScopedBehavior.Begin())
        {
            //Do stuff with scope here
        }
    }
}

public sealed class MyScopedBehavior : IDisposable
{
    private MyScopedBehavior()
    {
        //Start of scope behavior
    }

    public void Dispose()
    {
        //I only want to execute the following if we're not unwinding
        //through finally due to an exception:
        //...End of scope behavior    
    }

    public static MyScopedBehavior Begin()
    {
        return new MyScopedBehavior();
    }
}

Ada beberapa cara lain yang dapat saya lakukan untuk ini (berikan delegasi ke fungsi yang mengelilingi panggilan dengan perilaku tertentu), tetapi saya ingin tahu apakah mungkin untuk melakukannya menggunakan IDisposable pola.


Sebenarnya, ini tampaknya telah ditanyakan dan dijawab sebelumnya sini. Ada kemungkinan untuk mendeteksi dengan cara yang sangat kasar. Saya tidak akan benar-benar menggunakan teknik itu, tetapi menarik untuk mengetahui bahwa itu mungkin.


32
2017-07-21 16:21


asal


Jawaban:


Cara mencapai hal ini yang pernah saya lihat membutuhkan metode tambahan:

public static void MyMethod()
{
    using (var scope = MyScopedBehavior.Begin())
    {
        //Do stuff with scope here
        scope.Complete(); // Tells the scope that it's good
    }
}

Dengan melakukan ini, objek lingkup Anda dapat melacak apakah itu membuang karena kesalahan, atau operasi yang sukses. Ini adalah pendekatan yang diambil TransactionScope, misalnya (lihat TransactionScope.Complete).


16
2017-07-21 16:27



Sebagai titik sampingan, IL memungkinkan Anda untuk menentukan SEH fault blok yang mirip dengan finally tetapi dimasukkan hanya ketika pengecualian dilempar - Anda dapat melihat contoh sini, sekitar 2 / 3rds ke bawah halaman. Sayangnya, C # tidak mengekspos fungsi ini.


13
2017-07-21 16:44



Saya mencari sesuatu yang serupa untuk pengujian unit - Saya memiliki kelas pembantu yang saya gunakan untuk membersihkan objek setelah uji coba dan saya ingin menjaga sintaks 'menggunakan' yang bagus dan bersih. Saya juga ingin opsi untuk tidak membersihkan jika tes gagal. Apa yang saya dapatkan adalah menelepon Marshal.GetExceptionCode (). Saya tidak tahu apakah ini sesuai untuk semua kasus, tetapi untuk kode uji tampaknya berfungsi dengan baik.


7
2017-10-07 17:19



Yang terbaik yang bisa saya dapatkan adalah:

using (var scope = MyScopedBehavior.Begin())
{
  try
  {
    //Do stuff with scope here
  }
  catch(Exception)
  {
    scope.Cancel();
    throw;
  }
}

Tentu saja, scope.Cancel() akan memastikan tidak ada yang terjadi di Dispose ()


5
2017-07-21 16:27



Pola berikut menghindari masalah dengan penyalahgunaan API yaitu metode penyelesaian bidang tidak disebut dihilangkan sepenuhnya, atau tidak dipanggil karena kondisi logis. Saya pikir ini menjawab pertanyaan Anda lebih dekat dan bahkan lebih sedikit kode untuk pengguna API.

Edit

Bahkan lebih mudah setelah komentar Dan:

public class Bling
{
    public static void DoBling()
    {
        MyScopedBehavior.Begin(() =>
        {
            //Do something.
        }) ;
    }   
}

public static class MyScopedBehavior
{
    public static void Begin(Action action)
    {
        try
        {
            action();

            //Do additonal scoped stuff as there is no exception.
        }
        catch (Exception ex)
        {
            //Clean up...
            throw;
        }
    }
}   

3
2017-07-21 16:49



Saya pikir cara terbaik adalah menggunakan menulis try/catch/finally klausa secara manual. Mempelajari item dari buku pertama 'Effective c # ". Peretas C # yang baik harus tahu persis apa yang perlu diperluas. Ini telah berubah sedikit sejak .Net 1.1 - Anda sekarang dapat memiliki beberapa menggunakan satu di bawah yang lain. Jadi, gunakan reflektor, dan mempelajari kode un-sugared.

Kemudian, ketika Anda menulis kode Anda sendiri - baik gunakan using atau tulis barang Anda sendiri. Itu tidak terlalu sulit, dan hal yang baik untuk diketahui.

Anda bisa menjadi suka dengan trik lain, tetapi terasa terlalu berat, dan bahkan tidak efisien. Biarkan saya memasukkan contoh kode.

CARA LAIN:

using (SqlConnection cn = new SqlConnection(connectionString))
using (SqlCommand cm = new SqlCommand(commandString, cn))
{
    cn.Open();
    cm.ExecuteNonQuery();
}

CARA MANUAL:

bool sawMyEx = false;
SqlConnection cn =  null;
SqlCommand cm = null;

try
{
    cn = new SqlConnection(connectionString);
    cm = new SqlCommand(commandString, cn);
    cn.Open();
    cm.ExecuteNonQuery();
}
catch (MyException myEx)
{
    sawMyEx = true; // I better not tell my wife.
    // Do some stuff here maybe?
}
finally
{
    if (sawMyEx)
    {
        // Piss my pants.
    }

    if (null != cm);
    {
        cm.Dispose();
    }
    if (null != cn)
    {
        cn.Dispose();
    }
}

1
2017-07-21 16:26



Itu akan (IMHO sangat) membantu jika ada varian IDisposable yang Dispose metode menerima parameter untuk menunjukkan pengecualian apa, jika ada, yang tertunda ketika dijalankan. Di antara hal-hal lain, dalam hal itu Dispose tidak dapat melakukan pembersihan yang diharapkan, itu akan mampu melempar pengecualian yang mencakup informasi tentang pengecualian sebelumnya. Itu juga akan memungkinkan Dispose metode untuk membuang pengecualian jika kode "lupa" untuk melakukan sesuatu yang seharusnya dilakukan dalam using memblokir, tetapi tidak menimpa pengecualian lain yang mungkin menyebabkan penggunaan blok untuk keluar sebelum waktunya. Sayangnya, belum ada fitur seperti itu.

Ada banyak artikel yang menyarankan cara menggunakan fungsi API untuk mengetahui apakah ada pengecualian yang tertunda. Salah satu masalah utama dengan pendekatan semacam itu adalah mungkin kode itu berjalan di a finally memblokir untuk try yang berhasil diselesaikan, tetapi itu mungkin bersarang di finally blokir siapa try keluar secara prematur. Bahkan jika a Dispose Metode dapat mengidentifikasi bahwa situasi seperti itu ada, tidak akan bisa mengetahui yang mana try blok itu "milik" untuk. Seseorang dapat merumuskan contoh-contoh di mana situasi diterapkan.

Karena itu, pendekatan terbaik mungkin adalah memiliki metode "sukses" yang eksplisit dan menganggap kegagalan jika tidak disebut, dan menganggap bahwa konsekuensi dari lupa untuk memanggil metode "sukses" harus jelas bahkan jika tidak ada pengecualian yang dilemparkan. Satu hal yang dapat membantu sebagai metode utilitas sederhana akan menjadi sesuatu seperti

T Success<T>(T returnValue)
{
  Success();
  return T;
}

sehingga memungkinkan kode seperti:

return scopeGuard.Success(thingThatMightThrow());

daripada

var result = thingThatMightThrow();
scopeGuard.Success();
return result;

1
2018-06-05 15:59



Mengapa tidak dibuang begitu saja dari dalam a try { } memblokir di akhir, dan tidak menggunakan akhirnya sama sekali? Ini tampaknya perilaku yang Anda cari.

Ini juga tampak lebih realistis dalam hal bagaimana orang lain dapat menggunakan kelas Anda. Apakah Anda yakin bahwa setiap orang yang pernah menggunakannya tidak akan pernah mau membuang dalam kasus pengecualian? Atau haruskah perilaku ini ditangani oleh konsumen kelas?


0
2017-07-21 16:30