Являются ли объекты IDisposable предназначенными для создания и удаления только один раз? - PullRequest
2 голосов
/ 23 февраля 2010

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

При каждом обращении к узлу этот конкретный узел и его дочерние элементы «просыпаются» и лениво получают назначенные им ресурсы. Аналогичным образом, доступ к другой ветви в дереве приведет к тому, что текущая активная ветвь перейдет в спящий режим, что приведет к высвобождению ее ресурсов. Это означает, что любой заданный узел можно разбудить и перевести в спящий режим снова и снова в любой момент времени.

В настоящее время я использую интерфейс IDisposable для достижения этой цели. Это очень полезно, потому что во многих случаях мне нужно создавать небольшие ветви, которые будут использоваться локально, а ключевое слово «using» очень удобно, гарантируя, что ни один ресурс не останется открытым случайно.

Хорошо ли я реализую IDisposable на объектах, которые на самом деле не утилизируются, а как бы усыпляются?

Заранее спасибо.

Редактировать: Спасибо всем за все умные ответы. Мне понравилась идея избавиться от доступа к ресурсу вместо самого ресурса. Сейчас я нахожусь в поиске лучшего названия для функции, ответственной за очистку. (Любые идеи, кроме Release () или Sleep ()? Еще раз спасибо.

Ответы [ 6 ]

5 голосов
/ 23 февраля 2010

Это не совсем понятно из IDisposable.Dispose документации, которая включает в себя следующее (выделено мной):

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

но также это:

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

Последнее предполагает, что на самом деле не следует использовать для операции «перезагрузки», что, как я думаю, вам нужно. (Я не уверен, что ваша терминология «усыпить» здесь действительно помогает; правильно ли я сказал, что вы действительно распределяете все активно полученные ресурсы во всех подузлах?)

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

Я вижу, что вы пытаетесь сделать, но я не уверен, что лучший способ сделать это ...

4 голосов
/ 23 февраля 2010

@ Jon Skeet имеет действительно хорошо ответил на вопрос , но позвольте мне добавить комментарий, который, по моему мнению, должен быть ответом сам по себе.

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

Чтобы код выглядел более понятным для программиста, который его использует, вы должны использовать временное значение, которое вы use используете вместо объекта, который имеет ресурс, и возвращать его из имени метода, который сообщает программист, что он делает, т.е. временно получить некоторые ресурсы.

Позвольте мне показать пример.

Вместо этого:

using (node) { ... }

вы делаете это:

using (node.ResourceScope()) { ... }

Таким образом, вы фактически не утилизируете что-либо более одного раза, так как ResourceScope вернет новое значение, которым вы располагаете, и базовый узел останется как есть.

Вот пример реализации (непроверенный, ввод из памяти):

public class Node
{
    private Resource _Resource;

    public void AcquireResource()
    {
        if (_Resource == null)
            _Resource = InternalAcquireResource();
    }

    public void ReleaseResource()
    {
        if (_Resource != null)
        {
            InternalReleaseResource();
            _Resource = null;
        }
    }

    public ResourceScopeValue ResourceScope()
    {
        if (_Resource == null)
            return new ResourceScopeValue(this);
        else
            return new ResourceScopeValue(null);
    }

    public struct ResourceScopeValue : IDisposable
    {
        private Node _Node;

        internal ResourceScopeValue(Node node)
        {
            _Node = node;
            if (node != null)
                node.AcquireResource();
        }

        public void Dispose()
        {
            Node node = _Node;
            _Node = null;
            if (node != null)
                node.ReleaseResource();
        }
    }
}

Это позволяет вам сделать это:

Node node = ...
using (node.ResourceScope())     // first call, acquire resource
{
    CallSomeMethod(node);
}                                // and release it here

...
private void CallSomeMethod(Node node)
{
    using (node.ResourceScope()) // due to the code, resources will not be 2x acquired
    {
    }                            // nor released here
}

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

2 голосов
/ 23 февраля 2010

Я бы сказал «нет». IDisposable имеет определенную цель, и "спать" не так.

1 голос
/ 23 февраля 2010

Похоже, вам просто нужно добавить дополнительный уровень косвенности .

То, что здесь путают, это время жизни объекта. С одной стороны, у вас есть долгоживущий объект (node), который не всегда использует другие ресурсы. С другой стороны, у вас есть те другие ресурсы, которые узел может использовать, находясь в состоянии «бодрствования», и откажется (если я вас правильно понял) при переходе в спящий режим (в силу того, что выбран другой узел).

Похоже, у вас есть две концепции жизни, и их можно смоделировать, введя другой объект для более непосредственного управления ресурсами.

Итак, поместите ваши «лениво добытые ресурсы» в новый объект управления ресурсами, который сам охотно добывает ресурсы - и избавляется от них в dispose(). Тогда ваш узел может просто создавать объекты управления ресурсами по мере необходимости (при пробуждении) и утилизировать их после завершения (возврата в спящий режим) - и время жизни не путается.

1 голос
/ 23 февраля 2010

Нет, это не похоже на правильное использование IDisposable.

Быстрая мысль о том, что вы могли бы сделать;Реализуйте еще один объект IDisposable, который может содержать загруженные данные, и вернуть эти данные из вызова метода вашего объекта;например:

using(var wokenUpData = dataContainer.WakeUp())
{
    // access the data using wokenUpData
    ...
}
1 голос
/ 23 февраля 2010

Конечно, вы не должны освобождать ресурсы, если у вас их нет, поэтому в этом случае ваш метод Dispose ничего не сделает.

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

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

...