2009-12-16 8 views
8

Edytuj: Dwie opcje przedstawione poniżej.Jaki jest najlepszy sposób na odzyskanie skonstruowanych IDisposables bezpiecznie?

Jeśli jesteś tylko przy użyciu funkcjonalności, którą zapewnia IDisposable, trafnie nazwana klauzula using działa dobrze. Jeśli w obiekcie znajduje się opakowanie an IDisposable, sam obiekt zawierający musi być IDisposable i trzeba zaimplementować odpowiedni wzorzec (albo zapieczętowaną klasę IDisposable, albo program Messier, ale standard virtual pattern).

Ale czasami metoda fabryki pomocniczej jest dobra dla czystości. Jeśli zwrócisz IDisposable bezpośrednio po zakończeniu budowy, wszystko jest w porządku, ale jeśli najpierw je skonstruujesz, a następnie zmodyfikujesz lub w inny sposób wykonasz kod, który może wyrzucić wyjątek przed powrotem, musisz bezpiecznie zadzwonić pod numer .Dispose() - ale tylko, jeśli był błąd.

Na przykład, kod niebezpieczne może wyglądać tak ...

DbCommand CreateCommandUnsafely(string commandText) 
{ 
    var newCommand = connection.CreateCommand(); 
    newCommand.CommandText = commandText; //what if this throws? 
    return newCommand; 
}  

Solutions Dwa bezpieczne warianty następująco ...

DbCommand CreateCommandSafelyA(string commandText) 
{ 
    DbCommand newCommand = null; 
    bool success = false; 
    try { 
     newCommand = connection.CreateCommand(); 
     newCommand.CommandText = commandText; //if this throws... 
     success=true; 
     return newCommand; 
    } finally{ 
     if (!success && newCommand != null) 
      newCommand.Dispose(); //...we'll clean up here. 
    } 
} 


DbCommand CreateCommandSafelyB(string commandText) 
{ 
    DbCommand newCommand = null; 
    try { 
     newCommand = connection.CreateCommand(); 
     newCommand.CommandText = commandText; //if this throws... 
     return newCommand; 
    } catch { 
     if (newCommand != null) 
      newCommand.Dispose(); //...we'll clean up here. 
     throw; 
    } 
} 

Bezpieczne Wariant A jest już tylko jedna linia , ale wydaje się, że jest to podejście idiomatyczne. Nie wydaje się, aby istniały jakieś naprawdę zwięzłe rozwiązania, chociaż niektóre z poniższych plakatów podają niektóre opcje wykorzystujące lambdę, które wyodrębniają enkapsulację tej logiki.

Bloat kod z którymś z bezpiecznych metod pozostaje i jest szczególnie obciążające z kodem, który początkowo wyglądał jak ...

return new MyDisposableThing { 
    OptionA = "X", 
    OptionB = B.Blabla, 
    Values = src.Values.Where(priority => priority > 1.0), 
}; 

Powyższy kod pobiera napisany bezpiecznie jest trochę dłuższy i mniej czytelny ponieważ nie można już bezpiecznie używać skróconej składni ustawiającej.

Odpowiedz

4

Wierzę, że to jest średnia wzór:

DbCommand CreateCommand(string commandText) 
{ 
    DbCommand newCommand = null; 
    bool success = false; 
    try 
    { 
     newCommand = connection.CreateCommand(); 
     newCommand.CommandText = commandText; 
     success = true; 
     return newCommand; 
    } 
    finally 
    { 
     if (!success & newCommand != null) 
      newCommand.Dispose(); 
    } 
} 

To nie złapać i przekaż błąd.

+0

To wygląda jak kod, który widzę gdzie indziej. Czy wiesz, dlaczego jest to lepsze? –

+1

Łapanie arbitralnych wyjątków (jak to sugerują inne odpowiedzi) powoduje różnorodność problemów w szczególnych okolicznościach i powinno się ich unikać, gdy tylko jest to możliwe. Poprzez umieszczenie utylizacji wewnątrz instrukcji 'finally', standardowy wzorzec unika wychwytywania wyjątków i (gdy tworzenie obiektu się nie powiedzie) naśladuje zachowanie nominalnej instrukcji' using'. –

7

Nie - myślę, że nie ma lepszego sposobu.

Jednak można napisać klasę pomocniczą:

public static class DisposeHelper 
{ 
    public static TDisposable DisposeOnError<TDisposable>(TDisposable dispoable, Action<TDisposable> action) 
    where TDisposable : IDisposable 
    { 
    try 
    { 
     action(dispoable); 
    } 
    catch(Exception) 
    { 
     disposable.Dispose(); 
     throw; 
    } 

    return disposable; 
    } 
} 

Więc można napisać:

return DisposeHelper.DisposeOnError(connection.CreateCommand(), cmd => cmd.CommandText = commandText); 

Nie jestem pewien jednak, czy to jest naprawdę lepszy sposób.

2

Można rozważyć napisanie Extension Method:

public static class Disposable 
{ 
    public static void SafelyDo<T>(this T disp, Action<T> action) where T : IDisposable 
    { 
     try 
     { 
      action(disp); 
     } 
     catch 
     { 
      disp.Dispose(); 
      throw; 
     } 
    } 
} 

Pozwoliłoby to na pisanie kodu:

var disp = new MyDisposable(); 
disp.SafelyDo(d => 
    { 
     d.Foo = "Ploeh"; 
     d.Bar = 42; 
    }); 
return disp; 
0

Chyba jesteś overcomplicating problem.

Jeśli twoja metoda zwraca obiekt jednorazowy, wtedy mówisz: "Niniejszym rezygnuję z własności tego obiektu, na lepsze lub na gorsze". Jeśli wystąpi błąd podczas budowania, to dlaczego miałoby to coś zmienić? Kod wywołujący nadal będzie go usuwać, nawet jeśli rzucisz wyjątek.

Na przykład:

DbCommand CreateCommand(string commandText) { 
    var newCommand = connection.CreateCommand(); 
    newCommand.CommandText = commandText; // what if this throws? 
    return newCommand; 
} 

void UseCommand() { 
    using(var cmd = CreateCommand("my query goes here")) { 
     // consume the command 
    } 
} 

Edit: Niestety, jeśli jest wyjątek wewnątrz CreateCommand, zmienna cmd nigdy nie jest ustawiona, a obiekt nie zostanie prawidłowo umieszczony.

+3

Nie wierzę, że to prawda. Ponieważ zmienna cmd nigdy nie zostanie przypisana, blok używający nie wywoła polecenia cmd.Dispose(). –

+0

Problem polega na tym, że polecenie CommandText oznacza, że ​​polecenie newCommand nigdy nie jest zwracane do instrukcji using, więc nie zostanie tam usunięte. – Kleinux

+0

Dobra robota, chłopaki. W takim przypadku głosuję na coś podobnego do ogólnej metody winSharp93. –

Powiązane problemy