- ВКонтакте
- РћРТвЂВВВВВВВВнокласснРСвЂВВВВВВВВРєРСвЂВВВВВВВВ
- РњРѕР№ Р В Р’В Р РЋРЎв„ўР В Р’В Р РЋРІР‚ВВВВВВВВРЎР‚
- Viber
- Skype
- Telegram
Как правильно высвобождать частично созданные объекты в фабричном методе?
У меня есть класс MyClass
, который требует создания экземпляров A
, B
и C
для инициализации.
Все три класса наследуют IDisposable
. Я не владею исходным кодом классов A
, B
и C
, однако их конструкторы могут вызвать исключение при определённых комбинациях параметров.
Я знаю, что создавать исключение в конструкторе - это плохая практика, потому что в памяти может остаться класс в несогласованном состоянии (нарушение инварианта). Поэтому вместо использования прямого конструктора я использую фабричный метод MyClass.Create()
внутри которого происходит инициализация A
, B
и C
. Функция Create
генерирует исключение, если хотя-бы один из классов A
, B
и C
сгенерировал исключение. Я знаю, что если я не смог создать B
, то я должен как минимум освободить класс A
и поднять исключение. Также я знаю, что если я не смог создать класс C
, то я должен, по меньшей мере, освободить ранее созданные A
и B
, а затем поднять исключение в функции Create
.
Скажите пожалуйста, есть ли хороший паттерн или пример кода, как я могу очистить ранее инициализированные части в случае, когда мне не удалось собрать класс MyClass
?
Что если частей будет больше трёх? Я думаю об использовании стека Stack<IDisposable>
для очистки частей.
Добавляю минимально воспроизводимый пример по требованию
@user7860670. Похоже, что текст слишком сложен для восприятия:
class A: IDisposable
{
private readonly int p_value;
public A(int value)
{
ArgumentOutOfRangeException.ThrowIfNegative(value);
p_value = value;
}
public void Print()
{
Console.WriteLine($"Value is: {p_value}");
}
public void Dispose()
{
Console.WriteLine("A disposed");
}
}
class B : IDisposable
{
private readonly int p_value;
public B(int value)
{
ArgumentOutOfRangeException.ThrowIfLessThan(value, 10);
p_value = value;
}
public void Print()
{
Console.WriteLine($"Value is: {p_value}");
}
public void Dispose()
{
Console.WriteLine("B disposed");
}
}
class C : IDisposable
{
private readonly int p_value;
public C(int value)
{
ArgumentOutOfRangeException.ThrowIfLessThan(value, 20);
p_value = value;
}
public void Print()
{
Console.WriteLine($"Value is: {p_value}");
}
public void Dispose()
{
Console.WriteLine("C disposed");
}
}
class MyClass
{
private readonly A a;
private readonly B b;
private readonly C c;
protected MyClass(A _a, B _b, C _c) { a = _a; b = _b; c = _c;}
public MyClass Create(int _value)
{
// Maybe throw error
A a = new A(_value);
// Maybe throw error
// Need free A
B b = new B(_value);
// Maybe throw error
// Need free A
// Need free B
C c = new C(_value);
return new (a, b, c);
}
}
Ответы (1 шт):
Как насчёт такого кода?
public static MyClass Create(int value)
{
A a = null!;
B b = null!;
C c = null!;
try
{
a = new A(value);
b = new B(value);
c = new C(value);
}
catch
{
a?.Dispose();
b?.Dispose();
c?.Dispose();
throw new Exception("it's bad");
}
return new(a, b, c);
}
Но ещё лучше выкинуть фабричный метод и выполнять инициализацию в конструкторе:
class MyClass
{
private readonly A a;
private readonly B b;
private readonly C c;
public MyClass(int value)
{
try
{
a = new A(value);
b = new B(value);
c = new C(value);
}
catch
{
a?.Dispose();
b?.Dispose();
c?.Dispose();
throw new Exception("it's bad");
}
}
}
P.S. Сам класс MyClass
стоит сделать IDisposable
, и в его методе Dispose
освобождать a
, b
, c
.
class MyClass : IDisposable
{
...
public void Dispose()
{
a?.Dispose();
b?.Dispose();
c?.Dispose();
}
}