Объявить IDisposable для класса или интерфейса?

Исходя из следующей ситуации:

<code>public interface ISample
{
}

public class SampleA : ISample
{
   // has some (unmanaged) resources that needs to be disposed
}

public class SampleB : ISample
{
   // has no resources that needs to be disposed
}
</code>

Класс SampleA должен реализовывать интерфейс IDisposable для освобождения ресурсов. Вы можете решить это двумя способами:

1. Add the required interface to the class SampleA:

<code>public class SampleA : ISample, IDisposable
{
   // has some (unmanaged) resources that needs to be disposed
}
</code>

2. Add it to the interface ISample and force derived classes to implement it:

<code>public interface ISample : IDisposable
{
}
</code>

Если вы помещаете его в интерфейс, вы заставляете любую реализацию реализовывать IDisposable, даже если им нечего располагать. С другой стороны, очень ясно видеть, что конкретная реализация интерфейса требует блока dispose / using, и вам не нужно приводить его как IDisposable для очистки. В обоих случаях может быть больше плюсов / минусов ... почему вы предлагаете использовать один способ предпочтительнее другого?

 Damien_The_Unbeliever09 мая 2012 г., 13:28
Это вещь - если этоis исходя из фабрики, то скорее всего ваш кодis ответственный за утилизацию, поэтому я поместил его в интерфейс. Но если он впрыскивается, то я предполагаю, что инжектор также ответственен за срок службы, поэтомуwouldn't вписывается в интерфейс - я не думаю, что есть универсальный ответ на этот вопрос.
 Damien_The_Unbeliever09 мая 2012 г., 12:11
Насколько вероятно, что код, написанный на интерфейсе (предположительно передаваемый уже созданному экземпляру), ответственен заending (полезное) время жизни этого экземпляра?
 Beachwalker09 мая 2012 г., 13:25
@Damien_The_Unbeliever: Хорошо, если предположить, что ISample получен из результата фабричного метода или из-за внедрения зависимости.

Ответы на вопрос(7)

IFoo вероятно, следует реализоватьIDisposable если есть вероятность, что, по крайней мере, некоторые реализации будут реализованыIDisposableи, по крайней мере, в некоторых случаях последняя сохранившаяся ссылка на экземпляр будет сохранена в переменной или поле типаIFoo, Почти наверняка следует реализоватьIDisposable если какие-либо реализации могут реализоватьIDisposable и экземпляры будут созданы через интерфейс фабрики (как в случае с экземплярамиIEnumerator<T>, которые во многих случаях создаются через фабричный интерфейсIEnumerable<T>).

СравнениеIEnumerable<T> а такжеIEnumerator<T> поучительно Некоторые типы, которые реализуютIEnumerable<T> также реализоватьIDisposable, но код, который создает экземпляры таких типов, будет знать, что они есть, знает, что они нуждаются в удалении, и использовать их как свои конкретные типы. Такие экземпляры могут быть переданы другим подпрограммам как типIEnumerable<T>и эти другие процедуры не имеют ни малейшего представления о том, что объекты в конечном итоге будут нуждаться в утилизации, ноthose other routines would in most cases not be the last ones to hold references to the objects, Напротив, случаиIEnumerator<T> часто создаются, используются и в конечном итоге покидаются кодом, который ничего не знает о базовых типах этих экземпляров, за исключением того факта, что они возвращаютсяIEnumerable<T>, Некоторые реализацииIEnumerable<T>.GetEnumerator() возврат реализацииIEnumerator<T> будет утечка ресурсов, если ихIDisposable.Dispose Метод не вызывается до того, как они покинуты, и большинство из них принимает параметры типаIEnumerable<T> не будет возможности узнать, могут ли такие типы быть переданы ему. Хотя это было бы возможно дляIEnumerable<T> включить недвижимостьEnumeratorTypeNeedsDisposal указать, возвращен лиIEnumerator<T> должны быть расположены, или просто требуют, чтобы процедуры, которые вызываютGetEnumerator() проверьте тип возвращаемого объекта, чтобы увидеть, реализует ли онIDisposableэто быстрее и проще безоговорочно вызватьDispose метод, который может ничего не делать, чем определить,Dispose надо и называть это только если так.

 13 окт. 2017 г., 18:13
@binki: что сделало бы вещи чище, было бы дляIEnumerator реализоватьIDisposable, Код, который получаетIEnumerable он ничего не знает о том, должен предположить, что это может быть необходимо позвонитьDispose наIEnumerator это возвращается. Если он передает возвращенный перечислитель другому методу для последующего использования, последний может вызвать вызовDispose в теме. С призывами ничего не делатьDispose на что-то, что известно для реализацииIDisposable быстрее, чем проверка того, реализует ли объектIDisposable...
 13 окт. 2017 г., 17:44
IEnumerator<T> хороший пример интерфейса, где управление жизненным циклом определенно является частью его API. Вероятно, это было сделано для упрощения работы. Тем не менее, можно предположить, что кто-то хотел бы позвонитьGetEnumerator() в одном методе и в какой-то момент передать перечислитель другому методу. Этот другой метод в идеале не имел бы доступа кDispose(), Если рамки былиinterface IDisposableEnumerator<T> : IEnumerator<T>, IDisposable {} а такжеinterface IEnumerator<T> : IEnumerator { T Current { get; } }это может сделать некоторые вещи & # x201C; более чистыми & # x201D; (но и более сложный: - /).
 13 окт. 2017 г., 18:21
ИМО,IEnumerator<T> никогда не должен был реализовыватьсяIEnumerator во-первых, потому что существующий код будет вызыватьIEnumerable.GetEnumerator() и получитьIEnumerator<T> когда написано только для обработкиIEnumerator, Я все еще думаю отделить & # x201C; inner & # x201D; API потребления от & # x201C; external & # x201D; Управление жизненным циклом предпочтительнее, но я думаю, что оно работает достаточно хорошо для синхронных сценариев и сборов в памяти. Я пытаюсь решить свои проблемы с асинхронностью, разделив время жизни и & # x201C; потребитель & # x201D; API-интерфейсы местами, но для того, чтобы сделать то же самое со множеством, мне нужно было бы изобрести совершенно новый API.
 13 окт. 2017 г., 18:14
... самый простой способ для объектов выполнять свои обязательства, это вызватьDispose на счетчиков, которыми они владеют, прежде чем оставить их, независимо от того, будут ли эти счетчики заботиться.
 13 окт. 2017 г., 19:29
@binki: код C # дляforeach звонкиIDisposable если экземпляр возвращенGetEnumerator реализует это.

Хорошим примером двух являетсяIList.

IList означает, что вам нужно реализовать индексатор для вашей коллекции. Тем не менее,IList на самом деле также означает, что выIEnumerableи вы должны иметьGetEnumerator() для вашего класса.

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

Сфокусироваться наIDispoable В частности,IDispoable в частности, вынуждает программистов использовать ваш класс для написания довольно уродливого кода. Например,

foreach(item in IEnumerable<ISample> items)
{
    try
    {
        // Do stuff with item
    }
    finally
    {
        IDisposable amIDisposable = item as IDisposable;
        if(amIDisposable != null)
            amIDisposable.Dispose();  
    }
}

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

Вставил код, чтобы ответить на один из комментариев здесь, чтобы его было легче читать.

 09 мая 2012 г., 15:24
@Stegi, использующий блок, может не выглядеть уродливо, но он все равно будет иметь снижение производительности. Также я не вижу, как вы, например, используете блок использования внутри foreach. Код Microsoft содержит примеры, в которых это делается: IDisposable amIDisposable = object as IDisposable; if (amIDisposable! = null) amIDisposable.Dispose (); Поскольку as не создает исключение, если не может преобразовать его в IDisposable, снижение производительности практически отсутствует.
 09 мая 2012 г., 22:39
Даже если только 1% классов, которые реализуют или наследуют от определенного типа, будут делать что-либо вIDisposable.Dispose, если это когда-либо будет необходимо, чтобы позвонитьIDisposable.Dispose для переменной или поля, объявленного как этот тип (в отличие от одного из реализующего или производного типа), что является хорошим признаком того, что сам тип должен наследовать или реализовыватьIDisposable, Тестирование во время выполнения, реализует ли экземпляр объектаIDisposable и звонитIDisposable.Dispose на нем, если так (как это было необходимо с неуниверсальнымIEnumerable) является основным запахом кода.
 Beachwalker09 мая 2012 г., 13:19
Но что, если есть фабрики (или простое использование IoC-контейнера), которые поставляют объекты реализации с требованием их утилизации или без них? Вам придется вызывать Dispose (), вызывая и вызывая его явно. Таким образом, вашему коду нужны знания о (возможной) реализации ... какая-то связь? В противном случае вы можете просто использовать блок, который очень прост.

ISample«я должен быть одноразовым, я поставил бы его на интерфейс, если только некоторые из них я только поместил бы на те классы, где он должен быть».

Похоже, у вас есть последний случай.

 09 мая 2012 г., 13:30
@Stegi - Легко. Просто проверитьis IDisposable и позвонитьDispose если так.
 Beachwalker09 мая 2012 г., 13:46
Да, я знаю, но это будет мусором кода ... потому что КАЖДАЯ другая реализация (например, IList, I ...) может быть IDisposable. Мне кажется, IDisposable должен быть реализацией базового класса общего объекта (даже если он пустой).
 Beachwalker09 мая 2012 г., 13:26
Но как обеспечить вызов Dispose (), если пользователь вашей библиотеки не знает об этом и может потребоваться реализация с утилитой?
 09 мая 2012 г., 15:32
@Stegi, если вы согласны с влиянием попытки tryf на производительность
 09 мая 2012 г., 14:42
@Stegi - Тогда ты действительно ответил на свой вопрос.

Принцип разделения сегментов изSOLID если вы добавляете IDisposable к интерфейсу, который вы предоставляете, клиентам, которые не заинтересованы, вы должны добавить его в A.

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

Любой интерфейс может быть потенциально реализован с или без элементов, которые должны быть расположены.

 22 дек. 2018 г., 10:05
Также инструментам анализа кода легче предупреждать, если сами интерфейсы реализуют IDisposable.
 22 дек. 2018 г., 10:03
Хотя в теории ваше предложение является наиболее правильным, на практике работа с интерфейсами с потенциальными одноразовыми реализациями является обременительной. Многое зависит от того, кто управляет временем жизни объекта. Лучший ответ ИМО здесь:stackoverflow.com/a/14368765/661933
 04 янв. 2019 г., 09:52
@nawfal: если теория не совпадает с практикой, это означает, что инструменты, которые мы используем, неверны, а не наоборот. Интерфейс по определению не может быть одноразовым, поскольку вы располагаете только реализациями, а не контрактами на эксплуатацию.
Решение Вопроса

using(){} для всех ваших интерфейсов лучше всего иметьISample вытекают изIDisposable потому что эмпирическое правило при разработке интерфейсов в пользу"ease-of-use" над"ease-of-implementation".

 23 мая 2012 г., 10:56
как бы вы использовали оператор использования в foreach?
 18 июн. 2012 г., 03:05
& quot; ... и вы только видите интерфейс ... & quot; Это ключ. Многие хорошие решения ООП будут использовать только интерфейс, поэтому я думаю, что для интерфейса имеет смысл реализовать IDisposable. Таким образом, потребительский код может обрабатывать все подклассы одинаково.

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

 08 мар. 2016 г., 09:24
Это просто не соответствует действительности. Если в вашем классе есть поле, которое реализует IDisposable, то этот класс также должен быть IDisposable, чтобы вызвать Dispose для поля. Если вы добавляете IDisposable, когда он не нужен, в итоге половина ваших классов становится IDisposable.

IDisposable на интерфейсе может вызвать некоторые проблемы. Это означает, что время жизни всех объектов, реализующих этот интерфейс, может быть безопасно завершено синхронно. Т.е. он позволяет любому писать код, подобный этому, и требуетall реализации для поддержкиIDisposable:

using (ISample myInstance = GetISampleInstance())
{
    myInstance.DoSomething();
}

Только код, который обращается к конкретному типу, может знать правильный способ управления временем жизни объекта. Например, тип может не нуждаться в удалении в первую очередь, он может поддерживатьIDisposableили это может потребоватьawaiting какой-то асинхронный процесс очистки после того, как вы его используете (например,что-то вроде варианта 2 здесь).

Автор интерфейса не может предсказать все возможные будущие потребности управления временем жизни / областью применения классов реализации. Назначение интерфейса - позволить объекту предоставлять некоторый API, чтобы он мог быть полезен для некоторого потребителя. Некоторые интерфейсыmay быть связаны с управлением жизненным циклом (например,IDisposable само по себе), но смешивание их с интерфейсами, не связанными с управлением временем жизни, может сделать написание реализации интерфейса трудной или невозможной. Если у вас очень мало реализаций вашего интерфейса и вы структурируете свой код так, чтобы потребитель интерфейса и менеджер времени существования / области видимости находились в одном методе, то это различие сначала не ясно. Но если вы начнете передавать свой объект, это будет яснее.

void ConsumeSample(ISample sample)
{
    // INCORRECT CODE!
    // It is a developer mistake to write “using” in consumer code.
    // “using” should only be used by the code that is managing the lifetime.
    using (sample)
    {
        sample.DoSomething();
    }

    // CORRECT CODE
    sample.DoSomething();
}

async Task ManageObjectLifetimesAsync()
{
    SampleB sampleB = new SampleB();
    using (SampleA sampleA = new SampleA())
    {
        DoSomething(sampleA);
        DoSomething(sampleB);
        DoSomething(sampleA);
    }

    DoSomething(sampleB);

    // In the future you may have an implementation of ISample
    // which requires a completely different type of lifetime
    // management than IDisposable:
    SampleC = new SampleC();
    DoSomething(sampleC);
    sampleC.Complete();
    await sampleC.Completion;
}

class SampleC : ISample
{
    public void Complete();
    public Task Completion { get; }
}

В приведенном выше примере кода я продемонстрировал три типа сценариев управления временем жизни, добавив к двум предоставленным вами.

SampleA is IDisposable with synchronous using () {} support. SampleB uses pure garbage collection (it does not consume any resources). SampleC uses resources which prevent it from being synchronously disposed and requires an await at the end of its lifetime (so that it may notify the lifetime management code that it is done consuming resources and bubble up any asynchronously encountered exceptions).

Отделяя управление временем жизни от других интерфейсов, вы можете предотвратить ошибки разработчика (например, случайные вызовыDispose()) и более четко поддерживать будущие непредвиденные шаблоны управления временем жизни / областью действия.

 13 окт. 2017 г., 18:18
Типы, которые не требуют очистки, могут быть реализованыIDisposable легко и правильно с помощьюvoid IDisposable.Dispose() {};, Если некоторым объектам, возвращенным из фабричной функции, потребуется очистка, проще и проще вернуть ей тип, который реализуетIDisposable и требуют, чтобы клиенты уравновешивали каждый вызов фабричной функции с вызовом возвращаемого объектаDispose чем требовать, чтобы клиенты определяли, какие объекты нуждаются в очистке.

Ваш ответ на вопрос