Я постараюсь ответить на свой вопрос:
Избегайте в первую очередь
Самый простой выход из этой ситуации - это рефакторинг кода, чтобы полностью избежать проблемы.
Есть два очевидных способа сделать это.
Создание внешнего экземпляра
Если AContainer
не создает экземпляр SomeDisposableObject
, а вместо этого использует внешний код для его предоставления, то AContainer
больше не будет "владеть" экземпляром и не будет отвечать за его удаление.
Внешний экземпляр может быть предоставлен через конструктор или путем установки свойства.
public class AContainerClass
{
SomeDisposableObject m_someObject; // No creation here.
public AContainerClass(SomeDisposableObject someObject)
{
m_someObject = someObject;
}
public SomeDisposableObject SomeObject
{
get { return m_someObject; }
set { m_someObject = value; }
}
}
Хранить экземпляр в секрете
Основная проблема с размещенным кодом заключается в том, что право собственности перепутано. Во время утилизации класс AContainer
не может определить, кому принадлежит экземпляр. Это может быть экземпляр, который он создал, или это может быть какой-то другой экземпляр, который был создан извне и set
через свойство.
Даже если он отслеживает это и точно знает, что имеет дело с созданным им экземпляром, он все равно не может безопасно утилизировать его, поскольку другие классы теперь могут иметь ссылку на него, полученную ими. из государственной собственности.
Если код можно реорганизовать, чтобы избежать обнародования экземпляра (т. Е. Путем полного удаления свойства), тогда проблема исчезнет.
И если этого не избежать ...
Если по какой-то причине код не может быть реорганизован таким образом (как я об этом говорил в этом вопросе), то, по моему мнению, у вас остается довольно трудный выбор дизайна.
Всегда распоряжаться экземпляром
Если вы выберете этот подход, тогда вы фактически объявите, что AContainer
станет владельцем экземпляра SomeDisposableObject
, когда свойство будет установлено.
Это имеет смысл в некоторых ситуациях, особенно когда SomeDisposableObject
явно временный или подчиненный объект. Однако это должно быть тщательно задокументировано, так как требуется, чтобы вызывающий код был осведомлен об этой передаче права собственности.
(может быть более целесообразно использовать метод, а не свойство, поскольку имя метода может использоваться для дополнительной подсказки о владении).
public class AContainerClass: IDisposable
{
SomeDisposableObject m_someObject = new SomeDisposableObject();
public SomeDisposableObject SomeObject
{
get { return m_someObject; }
set
{
if (m_someObject != null && m_someObject != value)
m_someObject.Dispose();
m_someObject = value;
}
}
public void Dispose()
{
if (m_someObject != null)
m_someObject.Dispose();
GC.SuppressFinalize(this);
}
}
Только утилизировать, если все еще оригинальный экземпляр
При таком подходе вы будете отслеживать, был ли экземпляр изменен с первоначально созданного AContainer
, и утилизировать его только тогда, когда он был оригиналом. Здесь модель собственности смешана. AContainer
остается владельцем собственного экземпляра SomeDisposableObject
, но если предоставляется внешний экземпляр, то ответственность за его удаление несет внешний код.
Этот подход лучше всего отражает реальную ситуацию здесь, но его может быть трудно реализовать правильно. Код клиента все еще может вызывать проблемы, выполняя такие операции:
AContainerClass aContainer = new AContainerClass();
SomeDisposableObject originalInstance = aContainer.SomeObject;
aContainer.SomeObject = new SomeDisposableObject();
aContainer.DoSomething();
aContainer.SomeObject = originalInstance;
Здесь новый экземпляр был заменен, вызван метод, а затем восстановлен исходный экземпляр. К сожалению, AContainer
будет вызывать Dispose()
в исходном экземпляре при его замене, поэтому теперь он недействителен.
Просто сдавайся и пусть GC справится с этим
Это явно не идеально. Если класс SomeDisposableObject
действительно содержит какой-то дефицитный ресурс, то немедленное его уничтожение определенно вызовет у вас проблемы.
Однако он также может представлять наиболее надежный подход с точки зрения взаимодействия клиентского кода с AContainer
, поскольку не требует специальных знаний о том, как AContainer
относится к владению экземпляром SomeDisposableObject
.
Если вы знаете, что одноразовых ресурсов в вашей системе на самом деле недостаточно, то это может быть лучшим подходом.
Некоторые комментаторы предположили, что может быть возможно использовать подсчет ссылок для отслеживания, если какие-либо другие классы все еще имеют ссылку на экземпляр SomeDisposableObject
. Это было бы очень полезно, поскольку позволило бы нам распоряжаться им только тогда, когда мы знаем, что это безопасно, а в противном случае просто позволяем GC справиться с этим.
Однако я не знаю ни одного C # /. NET API для определения количества ссылок на объект. Если есть, пожалуйста, дайте мне знать.