Подтвердить что ты не робот

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

Начиная с следующей ситуации:

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
}

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

1. Добавьте требуемый интерфейс к классу SampleA:

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

2. Добавьте его в интерфейс ISample и принудительно создайте классы для его реализации:

public interface ISample : IDisposable
{
}

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

4b9b3361

Ответ 1

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

Ответ 2

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

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

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

Ответ 3

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

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

Ответ 4

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

Сравнение IEnumerable<T> и IEnumerator<T> является поучительным. Некоторые типы, которые реализуют IEnumerable<T>, также реализуют IDisposable, но код, который создает экземпляры таких типов, будет знать, что они собой представляют, знать, что они нуждаются в утилизации, и использовать их в качестве их конкретных типов. Такие экземпляры могут быть переданы другим подпрограммам как тип IEnumerable<T>, и эти другие подпрограммы не будут иметь понятия, что в конечном итоге объекты будут нуждаться в утилизации, но эти другие процедуры в большинстве случаев не будут последними ссылки на объекты. Напротив, экземпляры IEnumerator<T> часто создаются, используются и в конечном итоге забрасываются кодом, который ничего не знает о базовых типах этих экземпляров, кроме того, что они возвращаются IEnumerable<T>. Некоторые реализации возвратных реализаций IEnumerable<T>.GetEnumerator() IEnumerator<T> будут утечки ресурсов, если их метод IDisposable.Dispose не вызывается до того, как они будут оставлены, и большинство кода, принимающего параметры типа IEnumerable<T>, не смогут узнать, могут ли такие типы быть переданным ему. Хотя было бы возможно, чтобы IEnumerable<T> включил свойство EnumeratorTypeNeedsDisposal, чтобы указать, нужно ли было бы вернуть возвращенный IEnumerator<T> или просто потребовать, чтобы подпрограммы, вызывающие GetEnumerator(), проверяли тип возвращаемого объекта, чтобы увидеть, он реализует IDisposable, быстрее и проще безоговорочно вызывать метод Dispose, который может ничего не делать, чем определить, требуется ли Dispose и вызывать его, только если это так.

Ответ 5

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

Ответ 6

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

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

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

Автор интерфейса не может предсказать все возможные будущие потребности управления временем жизни/областью применения классов реализации. Назначение интерфейса - позволить объекту предоставлять некоторый API, чтобы он мог быть полезен для некоторого потребителя. Некоторые интерфейсы могут быть связаны с управлением временем жизни (например, самим 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();
    try
    {
        DoSomething(sampleC);
    }
    finally
    {
        sampleC.Complete();
        await sampleC.Completion;
    }
}

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

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

  1. SampleA является IDisposable с синхронным using() {} поддержки.
  2. SampleB использует чистую сборку мусора (он не потребляет никаких ресурсов).
  3. SampleC использует ресурсы, которые препятствуют его синхронной утилизации, и требует await в конце своего срока службы (чтобы он мог уведомить код управления временем жизни о том, что он выполнен, потребляя ресурсы, и обналичить любые асинхронно встречающиеся исключения).

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

Ответ 7

Лично я бы выбрал 1, если вы не составите конкретный пример для двоих. Хорошим примером двух является IList.

An 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() просто возвращается в реализацию.

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