Кто Избавляется от общественной собственности IDisposable?

После просмотра документации: https://beam.apache.org/releases/pydoc/2.11.0/apache_beam.io.gcp.pubsub.html я смог увидеть дополнительный аргумент перейти к ReadFromPubSub.

Требуется установить with_attributes = True, в противном случае вы просто получите поля данных.

Надеюсь, это поможет кому-то еще, кто может застрять или просто устать :)

28
задан GrahamS 23 March 2009 в 20:00
поделиться

10 ответов

Нет никакого единственного ответа, это зависит от Вашего сценария, и ключевой пункт является владением доступного ресурса, представленного свойством, как указывает Jon Skeet.

Иногда полезно посмотреть на примеры от Платформы.NET. Вот три примера, которые ведут себя по-другому:

  • Контейнер всегда располагает. Система. IO.StreamReader выставляет доступное свойство BaseStream. Это, как полагают, владеет базовым потоком, и расположение StreamReader всегда располагает базовый поток.

  • Контейнер никогда не располагает. Система. DirectoryServices. DirectoryEntry выставляет свойство Parent. Это, как полагают, не владеет своим родителем, таким образом расположение DirectoryEntry никогда не располагает своего родителя.

    В этом случае новый экземпляр DirectoryEntry возвращается каждый раз, когда свойство Parent разыменовывается, и вызывающая сторона, как по-видимому, ожидают, расположит его. Возможно это повреждает инструкции для свойств, и возможно должен быть GetParent () метод вместо этого.

  • Контейнер иногда располагает. Система. Данные. SqlClient. SqlDataReader выставляет доступное свойство Connection, но вызывающая сторона решает, владеет ли читатель (и поэтому располагает), базовое соединение с помощью аргумента CommandBehavior SqlCommand. ExecuteReader.

Другим интересным примером является Система. DirectoryServices. DirectorySearcher, который имеет чтение-запись доступное свойство SearchRoot. Если это свойство установлено снаружи, то базовый ресурс, как предполагается, не принадлежит, так не расположен контейнером. Если это не установлено снаружи, ссылка сгенерирована внутренне, и флаг установлен гарантировать, что это будет расположено. Вы видите это с Отражателем Лутца.

Необходимо решить, владеет ли контейнер ресурсом, и удостоверьтесь, что Вы документируете его поведение точно.

Если Вы действительно решаете владение ресурсом, и свойство является чтением-записью, необходимо удостовериться, что метод set располагает любую ссылку, которую это заменяет, например:

public SomeDisposableObject SomeObject    
{        
    get { return m_someObject; }        
    set 
    { 
        if ((m_someObject != null) && 
            (!object.ReferenceEquals(m_someObject, value))
        {
            m_someObject.Dispose();
        }
        m_someObject = value; 
    }    
}
private SomeDisposableObject m_someObject;

ОБНОВЛЕНИЕ: GrahamS справедливо указывает в комментариях, что лучше протестировать на m_someObject! = оцените в методе set перед расположением: я обновил вышеупомянутый пример для принятия во внимание это (использование ReferenceEquals, а не! =, чтобы быть явным). Хотя во многих реальных сценариях существование метода set могло бы подразумевать, что объект не принадлежит контейнеру и поэтому не будет расположен.

24
ответ дан Community 28 November 2019 в 03:11
поделиться

Это действительно зависит от того, кто умозрительно "владеет" доступным объектом. В некоторых случаях можно хотеть смочь передать в объекте, например, в конструкторе, без ответственности за берущий на себя класса за чистку его. Другие времена можно хотеть убрать его сами. При создании объекта (как в примере кода) затем, это должна почти наверняка быть обязанность очистить его.

Что касается свойства - я не думаю, имея свойство, должен действительно передать владение или что-либо как этот. Если Ваш тип ответственен за избавление от объекта, это должно сохранить ту ответственность.

14
ответ дан Jon Skeet 28 November 2019 в 03:11
поделиться

Настоящей проблемой могло бы быть Ваше объектно-ориентированное проектирование. Если AContainer Расположен, все его членские объекты должны также быть расположены. Если не это кажется, что можно расположить тело, но хотеть сохранить проживание экземпляра участка. Не звучит правильным.

5
ответ дан Igor Zelaya 28 November 2019 в 03:11
поделиться

Я попытаюсь ответить на свой собственный вопрос:

Избегайте его во-первых

Самый легкий выход из этой ситуации состоит в том, чтобы осуществить рефакторинг код для предотвращения проблемы полностью.
Существует два очевидных способа сделать это.

Внешнее создание экземпляра
Если AContainer не создает a SomeDisposableObject экземпляр, но вместо этого полагается на внешний код для предоставления его, затем AContainer больше не будет "владеть" экземпляром и не ответственен за избавление от него.

Внешне созданный экземпляр мог быть предоставлен через constuctor или путем установки свойства.

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 обработать его.

Однако я не знаю ни о каком API C#/.NET для определения подсчета ссылок объекта. Если существует тот, затем сообщенный мне.

3
ответ дан GrahamS 28 November 2019 в 03:11
поделиться

Если у Вас есть доступный объект на Вашем классе, Вы реализуете IDisposable с a Dispose метод, который располагает перенесенный disposables. Теперь код вызова должен гарантировать это using() используется или что эквивалент try / finally код, который располагает объект.

4
ответ дан Armin Ronacher 28 November 2019 в 03:11
поделиться

Причина Вы не можете безопасно звонить Dispose() на AContainerэкземпляр SomeDisposableObject происходит из-за отсутствия инкапсуляции. Общественная собственность предоставляет неограниченный доступ части внутреннего состояния. Поскольку эта часть внутреннего состояния должна соблюсти правила протокола IDisposable, важно удостовериться, что это хорошо инкапсулируется.

Проблема подобна предоставлению доступа к экземпляру, используемому для блокировки. Если Вы делаете это, становится намного более трудно определить, где блокировки получены.

Если можно постараться не выставлять доступный экземпляр проблема того, кто обработает вызов к Dispose() уходит также.

2
ответ дан Brian Rasmussen 28 November 2019 в 03:11
поделиться

Интересная вещь, с которой я встретился, состоит в том, что SqlCommand владеет SqlConnection (обе реализации IDisposable) экземпляр обычно. Однако вызов располагает на SqlCommand, НЕ расположит соединение также.

Я обнаружил это также с помощью Stackoverflow прямо здесь.

Так, другими словами, имеет значение, если "ребенок" (вложил?) экземпляр может / быть снова использованным позже.

1
ответ дан Community 28 November 2019 в 03:11
поделиться

В целом я думаю, кто бы ни создает объект, должно быть ответственно за Распоряжение. В этом случае AContainer создает SomeDisposableObject, таким образом, он должен быть Расположен, когда AContainer.

Если по некоторым причинам Вы думаете, что SomeDisposableObject должен жить дольше, чем AContainer - я могу только думать о следующих методах:

  • оставьте SomeDisposableObject не склонным, в этом случае GC будет заботиться о нем для Вас
  • дайте SomeDisposableObject ссылку на AContainer (см. свойства WinForms Controls и Parent). Пока SomeDisposableObject достижим, AContainer - также. Это будет препятствовать тому, чтобы GC Расположил AContainer, но если бы кто-то звонит, Располагают вручную - хорошо, Вы Расположили бы SomeDisposableObject. Я сказал бы, что это ожидается.
  • Реализуйте SomeDisposableObject как метод, скажите CreateSomeDisposableObject (). Это проясняет (er), что клиент ответственен за Распоряжение.

В целом, хотя - я не действительно уверен, что дизайн имеет смысл. В конце концов, Вы, кажется, ожидаете клиентский код как:

SomeDisposableObject d;
using (var c = new AContainer()) {
   d = c.SomeObject;
}
// do something with d

Это походит на взломанный клиентский код мне. Это нарушает Закон Demeter и простой здравый смысл мне.

0
ответ дан Mark Brackett 28 November 2019 в 03:11
поделиться

Дизайн, который Вы упомянули здесь, не является чем-то, мог обработать этот сценарий. Вы сказали, что существует контейнер для того класса затем, он должен расположить его наряду с собой. Если другие объекты могут использовать его затем, это не контейнер и объем Вас, класс расширяется, и это должно расположить на границе того объема.

0
ответ дан mamu 28 November 2019 в 03:11
поделиться

Вы могли просто отметить Распоряжение в, Располагают (). После того, как все Распоряжение не является деструктором - объект все еще существует.

так:

class AContainer : IDisposable
{
    bool _isDisposed=false;

    public void Dispose()
    {
        if (!_isDisposed) 
        {
           // dispose
        }
        _isDisposed=true;
    }
}

добавьте это к своему другому классу, также.

-1
ответ дан Program.X 28 November 2019 в 03:11
поделиться
Другие вопросы по тегам:

Похожие вопросы: