Определение того, должен ли IDisposable расширять интерфейс или быть реализован в классе, реализующем упомянутый интерфейс

Как я могу определить, должен ли я расширять один из моих интерфейсов с помощью IDisposable или реализовать IDisposable для класса, реализующего мой интерфейс?

У меня есть интерфейс, который не требует каких-либо внешних ресурсов, кроме одной конкретной реализации. Мои параметры выглядят так:

1) Внедрить IDisposable на интерфейс, требующий реализации всех реализаций Dispose, даже если это пустой метод.

-или -

2) Внедрить IDisposable только для классов, у которых есть ресурсы, которые необходимо утилизировать. Это вызовет проблемы с "использованием", потому что мой объект создан из factory, и поэтому весь восходящий код работает против интерфейса. Поскольку интерфейс не связан с IDisposable, "использование" не видит метод Dispose. Однако я мог бы привести результат factory к реализации; однако, что затем информирует потребителя о реализации, нарушая назначение интерфейсов.

Любые идеи относительно лучших практик?

Ответ 1

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

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

Хорошим примером первого варианта является IEnumerator. Многим объектам IEnumerator ничего не нужно делать, когда они расположены, но некоторые делают, и поэтому интерфейс расширяет IDisposable, потому что объект, ответственный за создание/жизненный цикл этого объекта, (или должен) никогда не будет иметь знаний базовой реализации.

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

Ответ 2

Вопрос в размере 50 000 долл. США заключается в том, будет ли когда-либо передаваться ответственность за удаление вместе с интерфейсом или, другими словами, может ли последний объект использовать объект-реализацию быть чем-то иным, чем создающая его организация.

Большая причина, по которой IEnumerator<T> реализует IDisposable, заключается в том, что реализующие объекты создаются объектами, которые реализуют IEnumerable<T>.GetEnumerator(), но затем обычно используются другими объектами. Объект, который реализует IEnumerable<T>, будет знать, действительно ли вещь, которую он возвращает, действительно нуждается в утилизации, но не будет знать, когда получатель будет с ней выполнен. Код, который вызывает IEnumerable<T>.GetEnumerator(), будет знать, когда это будет сделано с возвращенным объектом, но не будет знать, нужна ли ему какая-либо очистка. Разумная задача - указать, что код, который вызывает IEnumerable<T>.GetEnumerator(), необходим, чтобы гарантировать, что возвращенный объект будет иметь Dispose, вызываемый на нем, прежде чем он будет оставлен; метод Dispose во многих случаях ничего не сделает, но безоговорочный вызов метода do-nothing, который гарантированно существует, дешевле, чем проверка существования метода, который не существует.

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

Ответ 3

Если вы реализуете IDisposable в конкретном классе, и пользователь интерфейса знает, что он может быть одноразовым; вы можете сделать

IFoo foo = Factory.Create("type");
using(foo as IDisposable){
    foo.bar();
}

Если foo не реализует IDisposable, using будет равен using(null), который будет работать нормально.

Результат нижеприведенной программы будет

Fizz.Bar
Fizz.Dispose
Buzz.Bar

Пример программы

using System;

internal interface IFoo
{
    void Bar();
}
internal class Fizz : IFoo, IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Fizz.Dispose");
    }

    public void Bar()
    {
        Console.WriteLine("Fizz.Bar");
    }
}
internal class Buzz : IFoo
{
    public void Bar()
    {
        Console.WriteLine("Buzz.Bar");
    }
}

internal static class Factory
{
    public static IFoo Create(string type)
    {
        switch (type)
        {
            case "fizz":
                return new Fizz();
            case "buzz":
                return new Buzz();
        }
        return null;
    }
}

public class Program
{

    public static void Main(string[] args)
    {

        IFoo fizz = Factory.Create("fizz");
        IFoo buzz = Factory.Create("buzz");

        using (fizz as IDisposable)
        {
            fizz.Bar();
        }
        using (buzz as IDisposable)
        {
            buzz.Bar();
        }
        Console.ReadLine();
    }

}