C # Идентифицируемый вопрос - PullRequest
8 голосов
/ 13 декабря 2010

У меня есть следующий пример кода:

public interface IRepository {
   // Whatever
}

public class SampleRepository : IRepository {
   // Implements 'Whatever'
}

public class NHibernateRepository : IRepository, IDisposable {

   // ...

   public void Dispose() { ... }
}

Теперь - это действительно плохо? Я не уверен, но это похоже на не отмечать деструктор виртуального базового класса в C++.

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


Как следует обращаться с этим делом?

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

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

Спасибо.

Ответы [ 6 ]

15 голосов
/ 13 декабря 2010

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

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

Это точно такой же выбор, который делают Stream, TextReader и TextWriter в основном фреймворке. StringWriter (например) не нужно ничего утилизировать ... но TextWriter все еще одноразово.

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

9 голосов
/ 13 декабря 2010

Единственная проблема, с которой вы можете столкнуться, - это если вы используете какой-то тип фабрики, Inversion of Control или шаблон / каркас Dependency Injection.Если вы когда-нибудь захотите использовать объект в качестве интерфейса, вы никогда не сможете сделать это:

IRepository repo = Factory.GetRepository();
repo.Dispose();

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

4 голосов
/ 13 декабря 2010

Нет, не тяните IDisposable вверх. Сохраняйте интерфейсы атомарными, простыми и независимыми.

Если вы не можете утверждать, что IDisposable является фундаментальной чертой IRepository (а SampleRepository показывает, что это не так), то между ними не должно быть деривации.

3 голосов
/ 13 декабря 2010

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

По сути, владелец объекта IDisposable должен (*) либо позаботиться об удалении самого объекта, либо передать объект новому владельцу, который может принять и соблюдать ответственность. Первоначально IDisposable объекты, как правило, «принадлежат» их создателю, но передачи являются обычным явлением. Ссылка на объект IDispoable может быть передана другому объекту без передачи права собственности; в этом случае владелец по-прежнему несет ответственность за утилизацию объекта, когда он больше не нужен. Чтобы это произошло, владелец должен знать, что объект больше не нужен. Наиболее распространенные шаблоны:

  1. Объект передается в качестве параметра методу; объект, содержащий метод, не будет использовать объект после возврата метода.
  2. Объект передается в качестве параметра методу, который будет хранить его в поле какого-либо объекта, но позже этот объект может быть как-то запрошен для уничтожения ссылки.
  3. Одноразовый объект передается в качестве параметра методу, который размещается объектом, на который владелец одноразового объекта хранит все ссылки; хост-объект не будет использовать одноразовый объект без соответствующего запроса, и владелец одноразового объекта будет знать, не будет ли он больше выдавать такие запросы.

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

3 голосов
/ 13 декабря 2010

Мне кажется, это прекрасно. В чем проблема?

2 голосов
/ 13 декабря 2010

Сначала ответим на ваш вопрос. Шаблон dispose отличается от деструктора C ++. Метод Dispose предназначен для удаления ресурсов , содержащихся в классе , вместо удаления самого класса .

Причина пометки деструкторов C ++ как virtual не существует в .NET, потому что каждый экземпляр ссылочного типа имеет блок синхронизации, который содержит информацию о типе времени выполнения. Используя это, сборщик мусора может надлежащим образом восстановить правильный объем памяти.

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

Вместо этого рассмотрите возможность использования абстрактного базового класса, который реализует шаблон Dispose. Это будет соответствовать структуре «стандартной» реализации шаблона dispose.

public abstract class Repository : IDisposable  
{ 
    public void Dispose() { Dispose(true); }
    protected virtual Dispose(bool disposing) {  } 
}

public class NHibernateRepository : Repository { /* Impl here, add disposal. */ }

public class TestRepository : Repository { /* Impl with no resources */ }

Взгляните на Рекомендации по утилизации шаблонов , написанные Microsoft, чтобы увидеть более подробный пример.

...