Nano Hash - криптовалюты, майнинг, программирование

Кто распоряжается одноразовой общественной собственностью?

Если у меня есть класс SomeDisposableObject, который реализует IDisposable:

class SomeDisposableObject : IDisposable
{
    public void Dispose()
    {
        // Do some important disposal work.
    }
}

И у меня есть еще один класс AContainer, у которого есть экземпляр SomeDisposableObject в качестве общедоступного свойства:

class AContainer
{
    SomeDisposableObject m_someObject = new SomeDisposableObject();

    public SomeDisposableObject SomeObject
    {
        get { return m_someObject; }
        set { m_someObject = value; }
    }
}

Тогда FxCop будет настаивать на том, чтобы AContainer также был сделан IDisposable.

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

Как лучше всего избежать этого сценария?

(Предположим, что другой код полагается на AContainer.SomeObject, всегда имеющий ненулевое значение, поэтому простое перемещение создания экземпляра за пределы AContainer не является вариантом)

Изменить: я расскажу о некоторых примерах, так как я думаю, что некоторые комментаторы упускают из виду проблему. Если я просто реализую Dispose() метод на AContainer, который вызывает m_someObject.Dispose (), тогда у меня останутся следующие ситуации:

// Example One
AContainer container1 = new AContainer();
SomeDisposableObject obj1 = container1.SomeObject;
container1.Dispose();
obj1.DoSomething(); // BAD because obj1 has been disposed by container1.

// Example Two
AContainer container2 = new AContainer();
SomeObject obj2 = new SomeObject();
container2.SomeObject = obj2; // BAD because the previous value of SomeObject not disposed.
container2.Dispose();
obj2.DoSomething(); // BAD because obj2 has been disposed by container2, which doesn't really "own" it anyway.  

Это помогает?

23.03.2009

  • Сделав его свойством только для чтения, можно было бы сделать более ясным владение, но для другого класса по-прежнему будет возможно иметь ссылку на экземпляр m_SomeObject, когда AContainer удалит его. Так что возможность внесения ошибок все еще существует. 24.03.2009

Ответы:


1

Однозначного ответа нет, это зависит от вашего сценария, и ключевым моментом является владение одноразовым ресурсом, представленным свойством, как указывает Джон Скит.

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

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

  • Контейнер никогда не утилизируется. System.DirectoryServices.DirectoryEntry предоставляет свойство Parent. Он не считается владельцем своего родителя, поэтому удаление DirectoryEntry никогда не уничтожает его родителя.

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

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

Другой интересный пример - System.DirectoryServices.DirectorySearcher, у которого есть одноразовое свойство SearchRoot для чтения и записи. Если это свойство установлено извне, предполагается, что базовый ресурс не принадлежит, поэтому не удаляется контейнером. Если он не установлен извне, ссылка создается внутри, и устанавливается флаг, гарантирующий, что она будет удалена. Вы можете увидеть это с помощью Lutz Reflector.

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

Если вы все же решите, что являетесь владельцем ресурса, а свойство доступно для чтения / записи, вам необходимо убедиться, что ваш установщик использует любую ссылку, которую он заменяет, например:

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! = в установщике: я обновил приведенный выше пример, чтобы учесть это (используя ReferenceEquals, а не! = чтобы быть точным). Хотя во многих реальных сценариях существование установщика может означать, что объект не принадлежит контейнеру и, следовательно, не будет удален.

23.03.2009
  • Спасибо, Джо, +1, эти примеры действительно помогают показать, как эта ситуация может возникнуть в коде. Однозначно, похоже, нет однозначного ответа. 24.03.2009
  • В операции установки вы также должны проверить это (m_someObject! = Value) перед его удалением. 24.03.2009
  • Этот шаблон «сеттер» уродлив. Гораздо более понятным шаблоном было бы наличие метода SetTheProperty с параметром, указывающим, получит ли объект, которому передается IDisposable, право собственности, вместе с, возможно, методом ReleaseOwnershipOfThePropert, который будет принимать параметр типа свойства и устанавливать IsOwned значение true, если переданный параметр соответствует текущему значению свойства. 03.06.2011

  • 2

    Это действительно зависит от того, кто условно «владеет» предметом одноразового использования. В некоторых случаях вы можете захотеть передать объект, например, в конструктор, без того, чтобы ваш класс взял на себя ответственность за его очистку. В других случаях вы можете очистить его самостоятельно. Если вы создаете объект (как в примере кода), то почти наверняка вы должны его очистить.

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

    23.03.2009
  • Я согласен с тем, что недвижимость всегда должна принадлежать своему владельцу. В этом случае при отчуждении собственник ДОЛЖЕН избавиться от собственности. Если вы не хотите, чтобы это было так, вместо этого верните объект из функции. 23.03.2009
  • Согласитесь, это вопрос собственности Джон. Но избавление от объекта, используемого другим классом, кажется неожиданным побочным эффектом. А как насчет установленного метода? Если я устанавливаю новое значение, следует ли мне избавляться от старого? И следует ли мне позже избавиться от нового значения, даже если я его не создавал? 23.03.2009
  • @GrahamS: Бывают моменты, когда полезно иметь возможность передать право собственности на что-либо. Рассмотрим пару объектов IDisposable, представляющих аудиопоток и аудиоплеер. В некоторых случаях может быть полезно иметь возможность создавать поток и передавать его проигрывателю, при этом проигрыватель автоматически удаляет и себя, и поток, когда аудио завершается. В других контекстах можно иметь возможность воспроизводить аудиопоток несколько раз без необходимости каждый раз воссоздавать его, и может знать, когда он больше не будет заинтересован в его повторном воспроизведении. 03.06.2011
  • @GrahamS: Я бы сказал, что лучший подход - иметь явные средства передачи права собственности на объект, либо когда он передается, либо позже. Для последнего у меня был бы такой метод, как ReleaseOwnershipOfStream, который принимал бы параметр для потока. Если поток все еще воспроизводится, метод установит флаг на автоматическое удаление после завершения воспроизведения; если переданный поток все еще не воспроизводится, его следует немедленно удалить. 03.06.2011

  • 3

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

    23.03.2009
  • Обратитесь к примерам .NET Framework от Джо. Также подумайте, где у меня уже есть экземпляр SomeDisposableObject, с которым я затем устанавливаю AContainer.SomeObject. 24.03.2009
  • все его объекты-члены также должны быть удалены - ›это верно для композиции, а не для агрегирования. 08.12.2009

  • 4

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

    23.03.2009
  • При чистой настройке нет ссылок на внутренний одноразовый код, который мог бы зависеть от того, что объект все еще существует после удаления оболочки. Если вы зависите от этого поведения, рассмотрите возможность использования счетчика ссылок. 23.03.2009
  • Посмотрите на примеры .NET, предоставленные Джо. Как бы вы иначе спроектировали эти классы, чтобы они были чистыми? И как бы вы реализовали подсчет ссылок? 24.03.2009

  • 5

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

    Избегайте этого в первую очередь

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

    Создание внешнего экземпляра
    Если 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
  • У объектов нет встроенного счетчика ссылок в .NET. Вам придется реализовать это самостоятельно с помощью объекта-оболочки, который управляет подсчетом. 24.03.2009
  • У них уже должен быть какой-то счетчик ссылок, потому что именно так работает сборщик мусора, но я не думаю, что он доступен для кода. Очень сложно правильно реализовать собственный механизм подсчета ссылок. 24.03.2009
  • Ваша домашняя работа - прочитать все это: blogs.msdn.com/brada/articles/371015 .aspx 24.03.2009
  • (Краткая версия: GC! = Refcounting) 24.03.2009
  • Очень интересно спасибо Earwicker. Вот несколько хороших аргументов в пользу того, почему просто реализовать предложения по подсчету ссылок не удастся. 25.03.2009
  • Мне жаль, что .NET не предоставил универсальные средства реализации прокси-сервера подсчета владельцев, который не будет подсчитывать количество ссылок, существующих на объект (что было бы невозможно), но подсчитывал бы количество объектов-владельцев, которые были произведено, но не утилизировано. Такие вещи обычно не нужны для изменяемых ресурсов, но некоторые вещи, такие как Image, часто владеют ресурсами, даже если они неизменяемы. 08.12.2013

  • 6

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

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

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

    23.03.2009

    7

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

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

    Другими словами, имеет значение, может / будет ли повторно использован «дочерний» (вложенный?) Экземпляр позже.

    24.03.2009
  • В этом есть смысл. Команда обычно временная, но она использует соединение с более длительным сроком службы. 24.03.2009

  • 8

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

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

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

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

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

    Мне это кажется сломанным клиентским кодом. Это нарушает Закон Деметры и, как мне кажется, старый здравый смысл.

    23.03.2009
  • Согласились, что так выразились, это выглядит странно. Но не каждый IDisposable может уместить свое время жизни в пределах блока using. Также рассмотрим случай, когда у меня есть существующий экземпляр SomeDisposableObject, с которым я затем устанавливаю AContainer.SomeObject. 24.03.2009

  • 9

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

    23.03.2009

    10

    Вы можете просто пометить Disposal в Dispose (). В конце концов, Disposal не является деструктором - объект все еще существует.

    so:

    class AContainer : IDisposable
    {
        bool _isDisposed=false;
    
        public void Dispose()
        {
            if (!_isDisposed) 
            {
               // dispose
            }
            _isDisposed=true;
        }
    }
    

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

    23.03.2009
  • Не думаю, что это поможет. Я хочу сказать, что AContainer не может избавиться от m_someObject, потому что другие классы все еще могут его использовать. 23.03.2009
  • Новые материалы

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

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

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..