Определение того, должен ли 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();
}
}