Я постараюсь ответить на свой вопрос:
Избегайте этого в первую очередь
Самый простой выход из этой ситуации - провести рефакторинг кода, чтобы полностью избежать проблемы.
Есть два очевидных способа сделать это.
Создание внешнего экземпляра
Если 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; }
}
}
Сохраняйте частный доступ к экземпляру
Основная проблема с опубликованным кодом заключается в путанице с владельцем. Во время Dispose класс 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()
в исходном экземпляре, поэтому теперь он недействителен.
Просто откажитесь и позвольте сборщику мусора это сделать
Это явно далеко не идеально. Если класс SomeDisposableObject
действительно содержит какой-то дефицитный ресурс, то несвоевременная его утилизация определенно вызовет проблемы.
Однако он также может представлять наиболее надежный подход с точки зрения того, как клиентский код взаимодействует с AContainer
, поскольку он не требует специальных знаний о том, как AContainer
относится к владению экземпляром SomeDisposableObject
.
Если вы знаете, что одноразового ресурса в вашей системе на самом деле нет, это может быть лучшим подходом.
Некоторые комментаторы предположили, что можно использовать подсчет ссылок, чтобы отслеживать, есть ли еще какие-либо другие классы ссылки на экземпляр SomeDisposableObject
. Это было бы очень полезно, поскольку это позволило бы нам избавиться от него только тогда, когда мы знаем, что это безопасно, а в противном случае просто позволить GC обрабатывать это.
Однако мне неизвестны какие-либо C # /. NET API для определения счетчика ссылок объекта. Если есть, дайте мне знать.
24.03.2009