Всё же не разделяю ваше мнение. Пускай каждый сам взвесит все за и против и решит, что для него ближе.
По крайней мере существует сценарий совместного использования Expose/Dispose, который уже тесно перекликается со второй частью статьи. Например, дата-контракт сериализаторы, используемые для хранения состояния вью-моделей, не вызывают конструктора при создпании объекта (см. FormatterServices.GetUninitializedObject). Собственно из этого и родился паттерн Exposable. Если же ещё вью-модель имеет дело с unmaged-ресурсами, то очень логично использовать Dispose-метод, как минимум, в финализаторе. Но это уже спецефический случай, поэтому не привожу его в качестве примера.
Вы можете не принимать пример, но в нём не вижу ничего действительно опасного и нахожу более универсальным в плане использования. Хотя и ваш классический варинт хорош, поэтому не отрицаю его, как одно из решений.
Вы, вероятно, придерживаетесь консервативного стиля в программировании, когда объект пишется под конкретный сценарий использования (using). Мне же ближе вариант, когда объект можно использовать в различных сценарях даже в самых неожиданных.
Я вас уже некоторое время прошу привести пример такой необходимости.
Да просто протестировать соединение с сервером (иногда бывает такая кнопка в некоторых приложениях). Мне не нравятся такие примеры, но можно динамически переоткрыть соединение в целях информационной безопасности.
Мою реализацию вы можете использовать точно таким же образом с помощью using, как и свою. Никаких дополнительных утечек, про которые вы говорите, в таком случае не будет. Если же нужно более тонкое управление поведением установки соединения, а порой возникает и такая необходимость, то ваша реализация не столь удобна. Понятно, что если действительно возникла такая необходимость в тонкой настройке, то разработчик должен понимать, что делает, чтобы получить от этого выигрыш, но даже в случае ошибки ничего очень серьёзного из-за этого не произойдёт.
То есть представленная реализация более универсальна и покрывает вашу. Но чтобы получить от этой универсальности выигрыш нужно просто понимать, как всё работает, а какой-то сверхсложной логики тут нет, поэтому разберётся даже начинающий разработчик. И такой способ имеет место быть.
… и мы вернемся к ситуации «что будет, если вызвать GetSomeData до вызова Expose».
Это точно такая же ситуация, что и с вызовом метода после Dispose, и ничего в ней нет особенного. Но мне больше нравится оригинальный вариант, где даже после Dispose объект сохраняет способность к автоматическому восстановлению соединения.
А когда это произойдет для объекта, лежащего в сессии? Через полчаса после того, как пользователь покинет сайт? А сколько на сайте одновременных пользователей?
Нет ничего нерешаемого в данной ситуации, если это достаточно критично для приложения.
А смысл? Поведение между Expose/GetSomeData и просто GetSomeData ничем не отличается. Второе даже лучше, потому что сеодинение захватывается попозже.
С помощью вызовов Expose/Dispose можно более точно контролировать время жизни соединения, если это нужно. Более того так очень легко проверить доступность сервера базы данных (протестировать соединение).
Вы не понимаете, похоже. В моем случае не надо ничего оборачивать в try-catch (параноики могут обернуть вызов _conn.Open в конструкторе, но это не обязательно), потому что объект хоть и будет создан, но на него не будет ни одной ссылки, поэтому он будет сразу собран GC. А в вашем случае некорректно инициализированное соединение будет висеть внутри вашего объекта, и никакого способа избавиться от него нет (если, конечно, вы не реализуете try-catch внутри Expose).
Как так не нужно оборачивать? Если у вас в конструкторе возникнет исключение, а вы его нигде не обработаете, то приложение упадёт с UnhandledException. Или вы что-то другое имеете в виду?
А ничего, что TimeoutException бывает не только в таких случаях, и не всегда гарантировано, что соединение закрыто?
Не помню точного названия исключения, которое возникает при превышении интервала бездействия и автоматическом его разрыве сервером, но оно точно есть и отличается от остальных. Что-то вроде ConnectionHasBeenClosedByServer.
К сожалению, только неявно. Никакой безопасности в вашей реализации нет, более того, вы с каждой версией порождаете новые потенциальные утечки.
Давайте больше конкретики. В простейшем случае реализацию можно использовать почти точно так же, как и вашу, но при необходимости можно точно контролировать время жизни соединения.
Зато теперь все стало еще смешнее. Теперь при вызове GetSomeData незаметно для пользователя откроется соединение. А когда оно закроется? Никто не знает.
Если вас эта строчка очень смущает, то можете её убрать. Гарантировано Dispose вызовется, когда ссылок на объект не останется.
Но ладно бы у вас было неявное состояние, это еще можно понять, но какой тогда смысл в методе Expose? Можно просто вызывать GetSomeData, поведение будет ровно таким же.
Есть ещё свойство HasConnection, поэтому при желании можно контролировать наличие соединения методами Expose/Dispose, или можно не контролировать если лень или это не критично.
Собственно, один минус никуда не делся — что случится, если внутри Expose, на вызове _conn.Open, произойдет ошибка?
Вызов можно обернуть в try-catch блок и произвести нужные действия в зависимости от типа исключения. В вашем же случае нужно оборачивать в try-catch весь блок using, что уже смотрится не очень, поскольку последний тоже разворачивается в try-finally.
PPS Не надо при таймауте просто выкидвать коннекшн, вы его не освободили еще, и теперь он повиснет в пуле на некоторое неизвестное время.
Здесь имеется в виду превышение интервала бездействия по которому соединение закрылось автоматически.
На мой взгляд плюс реализации в том, что она неявно, но безопасно выполняет всю работу по установке соединения, и в то же время позволяет при необходимости её полностью контролировать.
Если внутри конструктора будет эксепшн, объект сразу попадет в очередь GC, где и будет радостно собран вместе со всеми его зависимостями (включая вызов финализатора, если тотопределен)
Говорят, что эксепшены в конструкторах — это не очень хорошая практика. Но на мой взгляд это не критично, поэтому пускай будет так.
Вызов GetSomeData до инициализации объекта невозможен
Это столь же очевидно, как невозможность вызвова этого метода сразу после исполнения Dispose.
Меня бы устроила примерно следующая реализация.
internal class UserDataProvider : IExposable, IDisposable
{
public Guid UserId { get; set; }
public IDictionary<string, object> SessionData { get; private set; }
private SqlConnection _conn;
public bool HasConnection { get { return _conn != null; } }
public UserDataProvider()
{
Expose();
}
~UserDataProvider()
{
Dispose();
}
public void Expose()
{
if (HasConnection) return;
_conn = new SqlConnection();
_conn.Open();
}
public void Dispose()
{
if (!HasConnection) return;
_conn.Dispose();
_conn = null;
}
public X GetSomeData()
{
if (!HasConnection) Expose();
try
{
var cmd = _conn.CreateCommand();
//...
}
catch (TimeoutException exception)
{
_conn = null;
return GetSomeData();
}
}
}
Всё гарантировано вызывается, покрывается ваш случай и даже более. Плюс объект становится возобновляемым. Какие минусы, на ваш взгляд, у такой реализации и чем она хуже классической?
В крайнем случае попробуйте использовать только один метод Expose, например, во вью-моделях, как описано в статье. Он помогает очень красиво организовать код и реализовать циклические инжекции.
Почему нет разделения ответственности? Всё так же, как и у вас, единственное отличие в том, что вместо new Provider(ConnectionString) будет _provider.Expose(). Если вам привычнее классический вариант, то используйте его, или назовите методы по-другому, суть паттерна останется прежней.
Мне нравится связка Expose/Dispose, непривычно, но ничего сверхсложного в ней тоже нет.
Или тривиально решается при помощи Expose/Dispose с тем же разделением ответственности, но только провайдер не создаётся заново каждый раз, а выходит из режима ожидания. Просто альтернативные способы со своими плюсами и минусами. И каждый имеет право на жизнь.
Пусть это будет провайдер данных для пользователя. Внутри он хранит информацию о сессии пользователя и кэш-данных. В Exposed состоянии (активная работа пользователя) держит соединение к БД, а в Disposed (простой или малая активность) ожидает запросов или использует только кэш.
Банально, ведь так работает телевизор или компьютер. Для быстрого включения, сохранения состояния (открытые приложения) и экономии ресурсов используется спящий режим. Почему не перенести такую аналогию в программирование, ведь это тоже своего рода паттерн проектирования бытовой и вычислительной техники…
Пускай есть объект, который достаточно дорог при создании и занимает ресурсы которые нужно переодически освобождать, а также важно его внутреннее состояние. То есть вариант с UOW и пулом не подходит, поскольку внутреннее состояние должно оставаться прежним. Совместное же использование Expose/Dispose решает эту проблему.
Если вы обнаружили что-то новое, используйте это новое, а не трогайте уже существующее. Вы правда не понимаете смысла понятия «паттерн»/«шаблон проектирования»?
Глупо соревноваться в том, кто лучше или хуже понимает понятие «паттерн». Просто мы с вами вкладываем в него несколько отличающийся смысл. Часто новое строится на основе уже существующего, для меня это как раз тот случай.
Соглашусь с вашими рассуждениями. Вероятно, подобрал не самый удачный пример для иллюстрации паттерна. Взгляните на вышестоящий комментарий habrahabr.ru/post/256629/#comment_8395113, чтобы лучше уловить суть того, что мне хотелось сказать.
В том-то и дело, что Dispose — это не деинициализация, это полный отказ от объекта.
В обычном понимании отказ, но если вдуматься, то не совсем так.Cсылка на объект вполне может оставаться и после вызова Dispose. Зачастую обращение к большинству свойств и методов вызовет исключение, если программист это предусмотрел, но экземпляр обычно запросто можно использовать в качестве ключа, вызывать ToString, Equals… Так почему бы не расширить понимание паттерна Disposable? Пусть метод приводит объект в дежурное состояние, когда он занимает меньше ресурсов, в спящий режим! Но тогда должен существовать и метод выводящий из этого состояния — Expose. Всё очень закономерно и логично. То есть мы получили некоторое обобщение паттерна Disposable, а ваш сценарий с отказом от объекта — это лишь его частный случай…
И вас вот вообще не смущает, что DbContext — это UOW, и его нельзя разделять между разными клиентами?
В статье упоминается про это, но задача стоит в том, чтобы показать альтернативу UOW.
На основании чего вы думаете, что создание и уничтожение DbContext — такая дорогая операция?
Это не дорогая операция в большинстве реальных случаев. Возможно, выбран не самый удачный пример, но он хорошо иллюстрирует концепцию совместного применения Expose и Dispose методов.
Это означает, что не «существует паттерн Exposable», а «я нашел что-то похожее на паттерн». Так вот, то, что вы пытаетесь выше показать для DbContext, называется object pooling, и реализуется не так. А то, что вы показываете во вью-моделях — это обычный инициализационный метод. И это два разных паттерна для решения разных задач.
Даже если классический object pooling выглядит по-другому, то это не означает, что альтернативных реализаций быть не может. Если Expose — обычный инициализационный метод, то Dispose — деинициализационный — всё просто :)
Но стоит учитывать те преимущества, которые даёт использование этого инициализационного метода перед классическим вызовом конструктора.
Хочу лишь спросить, не замечаете ли вы своего рода гармонии и красоты в существовании Expose и Dispose, конструктора и финализатора? Это, конечно, странный вопрос, но лично меня в программировании цепляет такая стройность…
По крайней мере существует сценарий совместного использования Expose/Dispose, который уже тесно перекликается со второй частью статьи. Например, дата-контракт сериализаторы, используемые для хранения состояния вью-моделей, не вызывают конструктора при создпании объекта (см. FormatterServices.GetUninitializedObject). Собственно из этого и родился паттерн Exposable. Если же ещё вью-модель имеет дело с unmaged-ресурсами, то очень логично использовать Dispose-метод, как минимум, в финализаторе. Но это уже спецефический случай, поэтому не привожу его в качестве примера.
Вы, вероятно, придерживаетесь консервативного стиля в программировании, когда объект пишется под конкретный сценарий использования (using). Мне же ближе вариант, когда объект можно использовать в различных сценарях даже в самых неожиданных.
Да просто протестировать соединение с сервером (иногда бывает такая кнопка в некоторых приложениях). Мне не нравятся такие примеры, но можно динамически переоткрыть соединение в целях информационной безопасности.
То есть представленная реализация более универсальна и покрывает вашу. Но чтобы получить от этой универсальности выигрыш нужно просто понимать, как всё работает, а какой-то сверхсложной логики тут нет, поэтому разберётся даже начинающий разработчик. И такой способ имеет место быть.
Это точно такая же ситуация, что и с вызовом метода после Dispose, и ничего в ней нет особенного. Но мне больше нравится оригинальный вариант, где даже после Dispose объект сохраняет способность к автоматическому восстановлению соединения.
Нет ничего нерешаемого в данной ситуации, если это достаточно критично для приложения.
С помощью вызовов Expose/Dispose можно более точно контролировать время жизни соединения, если это нужно. Более того так очень легко проверить доступность сервера базы данных (протестировать соединение).
Как так не нужно оборачивать? Если у вас в конструкторе возникнет исключение, а вы его нигде не обработаете, то приложение упадёт с UnhandledException. Или вы что-то другое имеете в виду?
Не помню точного названия исключения, которое возникает при превышении интервала бездействия и автоматическом его разрыве сервером, но оно точно есть и отличается от остальных. Что-то вроде ConnectionHasBeenClosedByServer.
Давайте больше конкретики. В простейшем случае реализацию можно использовать почти точно так же, как и вашу, но при необходимости можно точно контролировать время жизни соединения.
Если вас эта строчка очень смущает, то можете её убрать. Гарантировано Dispose вызовется, когда ссылок на объект не останется.
Есть ещё свойство HasConnection, поэтому при желании можно контролировать наличие соединения методами Expose/Dispose, или можно не контролировать если лень или это не критично.
Вызов можно обернуть в try-catch блок и произвести нужные действия в зависимости от типа исключения. В вашем же случае нужно оборачивать в try-catch весь блок using, что уже смотрится не очень, поскольку последний тоже разворачивается в try-finally.
Здесь имеется в виду превышение интервала бездействия по которому соединение закрылось автоматически.
На мой взгляд плюс реализации в том, что она неявно, но безопасно выполняет всю работу по установке соединения, и в то же время позволяет при необходимости её полностью контролировать.
Говорят, что эксепшены в конструкторах — это не очень хорошая практика. Но на мой взгляд это не критично, поэтому пускай будет так.
Это столь же очевидно, как невозможность вызвова этого метода сразу после исполнения Dispose.
Меня бы устроила примерно следующая реализация.
Всё гарантировано вызывается, покрывается ваш случай и даже более. Плюс объект становится возобновляемым. Какие минусы, на ваш взгляд, у такой реализации и чем она хуже классической?
Мне нравится связка Expose/Dispose, непривычно, но ничего сверхсложного в ней тоже нет.
Банально, ведь так работает телевизор или компьютер. Для быстрого включения, сохранения состояния (открытые приложения) и экономии ресурсов используется спящий режим. Почему не перенести такую аналогию в программирование, ведь это тоже своего рода паттерн проектирования бытовой и вычислительной техники…
Глупо соревноваться в том, кто лучше или хуже понимает понятие «паттерн». Просто мы с вами вкладываем в него несколько отличающийся смысл. Часто новое строится на основе уже существующего, для меня это как раз тот случай.
По крайней мере, вижу её там очень чётко.
Для своего круга задач вполне устраивает. Например пул потоков очень даже хорош.
Возможно, иногда стоит выходить за привычные границы, чтобы обнаружить что-то новое.
В обычном понимании отказ, но если вдуматься, то не совсем так.Cсылка на объект вполне может оставаться и после вызова Dispose. Зачастую обращение к большинству свойств и методов вызовет исключение, если программист это предусмотрел, но экземпляр обычно запросто можно использовать в качестве ключа, вызывать ToString, Equals… Так почему бы не расширить понимание паттерна Disposable? Пусть метод приводит объект в дежурное состояние, когда он занимает меньше ресурсов, в спящий режим! Но тогда должен существовать и метод выводящий из этого состояния — Expose. Всё очень закономерно и логично. То есть мы получили некоторое обобщение паттерна Disposable, а ваш сценарий с отказом от объекта — это лишь его частный случай…
В статье упоминается про это, но задача стоит в том, чтобы показать альтернативу UOW.
Это не дорогая операция в большинстве реальных случаев. Возможно, выбран не самый удачный пример, но он хорошо иллюстрирует концепцию совместного применения Expose и Dispose методов.
Даже если классический object pooling выглядит по-другому, то это не означает, что альтернативных реализаций быть не может. Если Expose — обычный инициализационный метод, то Dispose — деинициализационный — всё просто :)
Но стоит учитывать те преимущества, которые даёт использование этого инициализационного метода перед классическим вызовом конструктора.
Хочу лишь спросить, не замечаете ли вы своего рода гармонии и красоты в существовании Expose и Dispose, конструктора и финализатора? Это, конечно, странный вопрос, но лично меня в программировании цепляет такая стройность…
А если у вас циклические ссылки не двух, а трёх и более классов друг на друга?
Механизм Exposable позволяет красиво разрешить и такие ситуации.