Значение термина, вынесенного в заголовок, скорее всего никому кроме меня неизвестно. Это потому, что я сам его придумал, так как не знаю точного названия того, о чём хочу рассказать. А речь пойдёт о методах, которые обязательно должны быть вызваны вместе. Примерами таких служат ListBox.BiginUpdate() и ListBox.EndUpdate() или CodeAccessPermission.Assert() и CodeAccessPermission.RevertAssert().
Объединяет эти методы то, что вызвав первый из них (дальше я буду называть его Begin-вызовом) программист обязан (в случае, если вызов завершился успешно) вызвать и второй (я буду называть его End-вызов). Часто реализуют такие вызовы следующим образом:
try {
// Некоторая полезная работа
listbox.Items.Add("a");
listbox.Items.Add("b");
listbox.Items.Add("c");
} finally {
listBox.EndUpdate(); // End-вызов
}//try
то есть код после Begin-вызова заключают в блок try, а End-вызов в finally. Это позволяет (в скобках заметим, худо-бедно) гарантировать, что в случае возникновения исключения между вызовами объект, методы которого вызываются, останется в согласованном состоянии.
Конечно, использовать try-finally каждый раз при одинаковых вызовах очень неудобно. О способе избежать такого неудобства я и расскажу.
Обычно, для упрощения вызова “парных” методов, используют класс-хелпер, реализующий IDisposable Interface, и вызывающий в своей реализации Dispose() End-метод:
{
private readonly ListBox listBox;
public ListBoxUpdateHelper(ListBox listBox) {
if(listBox == null) {
throw new ArgumentNullException("listBox");
}//if
this.listBox = listBox;
ListBox.BeginUpdate();
}
private ListBox ListBox {
[DebuggerStepThrough]
get { return listBox; }
}
public void Dispose() {
ListBox.EndUpdate();
}
}
Теперь вызывать BeginUpdate и EndUpdate удобнее:
// Некоторая полезная работа
listbox.Items.Add("a");
listbox.Items.Add("b");
listbox.Items.Add("c");
}//using
Мне он не нравится по той причине, что требует необходимости завести новую сущность – тип-хелпер и всюду в месте применения её использовать. Для пользователя библиотеки это может оказаться сложным: что бы легко и удобно пользоваться некоей функциональностью типа (ListBox в нашем примере) надо “позвать на помощь” другой тип. А если различных парных методов требуется несколько? Несколько и типов-хелперов.
Поэтому прогрессивное сообщество пошло дальше, вместо типа-хелпера предоставив пользователю метод-хелпер:
{
public static IDisposable DoUpdate(this ListBox listBox) {
return new ListBoxUpdateHelper(listBox);
}
private sealed class ListBoxUpdateHelper : IDisposable
{
private readonly ListBox listBox;
public ListBoxUpdateHelper(ListBox listBox) {
if(listBox == null) {
throw new ArgumentNullException("listBox");
}//if
this.listBox = listBox;
ListBox.BeginUpdate();
}
private ListBox ListBox {
[DebuggerStepThrough]
get { return listBox; }
}
public void Dispose() {
ListBox.EndUpdate();
}
}
}
Который можно использовать так:
// Некоторая полезная работа
listbox.Items.Add("a");
listbox.Items.Add("b");
listbox.Items.Add("c");
}//using
Теперь “снаружи” виден лишь один метод DoUpdate(), по типу возвращаемого значения которого (IDisposable) вызывающий будет знать, что метод, по возможности, лучше вызвать в блоке using.
Указанный подход можно найти в большом количестве библиотек, например в R Smart Application Toolkit.
Но меня не устраивает и этот вариант. Вернее, громозкозть его реализации. Спустя какое-то время увлечения данной методикой мне надоело писать большое количество классов-хелперов (которые по прежнему остались в виде private) и я начал пользоваться таким вот классом:
{
public static IDisposable New(Action dispose) {
return new ActionDisposable(dispose);
}
[Serializable]
[DebuggerDisplay("{ Action != null ? \"<Action>\" : \"<Action (Empty)>\" }")]
private sealed class ActionDisposable : IDisposable
{
public ActionDisposable(Action action) {
Action = action;
}
private Action Action { get; set; }
public void Dispose() {
if(Action != null) {
Action();
Action = null;
}//if
}
}
}
С ним упростилось написание методов, аналогичных вышеприведённому DoUpdate():
{
public static IDisposable DoUpdate(this ListBox listBox) {
listBox.BeginUpdate();
return Disposable.New(listBox.EndUpdate);
}
}
Что позволяет быстрее и проще, а, значит, и чаще использовать описанный паттерн (не побоюсь этого слова :о) – а кстати, как его можно назвать?).
Подробнее о классе Disposable и ещё об одном обнаруженном с его помощью “паттерне” я раскажу как-нибудь в следующий раз.
P.S. Касательно “худо-бедно”: существует возможность того, что поток, в котором выполняется рассматриваемый нами код, будет прерван вызовом Thread.Abort(…) из другого потока сразу после вызова Begin-метода и перед тем, как начнётся try-блок. Источник: Locks and exceptions do not mix.
Большое спасибо, очень интересные приёмы =)
ОтветитьУдалитьОсобенный респект за ссылочку на "Locks and exceptions do not mix", срочно побежал переписывать код с Monitor'ами, только не понял почему в решении C#4.0 могут дедлоки возникать... =(
2Пельмешко: Переписывать надо не "код с Monitor'ами", а "код с Thread::Abort()".
ОтветитьУдалитьFred, я вот заметил, что на форуме RSDN в разделе .NET GUI когда возникают чуть более менее сложные вопросы, то зачастую отвечаете только вы. Я к тому, что можно в блоге если что на эту тему писать :)
ОтветитьУдалить@MozgC
ОтветитьУдалитьWinForms мне уже подчти совсем не интересны, а WPF я ещё только изучаю.
Остаются только вопросы баиндинга и прочего, связанного с компонентной моделью. Тут есть идея написать про класс Attribute и его роль в System.CompoentModel, но не уверен что это то, что ты имел в виду :о)
>WinForms мне уже подчти совсем не интересны
ОтветитьУдалитьЖаль, думаю WinForms еще долго проживет, одни программисты ленивы, другие просто медленны, третьи не будут переходить пока не появится достаточно информации и готовых решений по новой технологии. Да и компьютеры во многих компаниях просто не потянут..
>Тут есть идея написать про класс Attribute и его роль в System.CompoentModel, но не уверен что это то, что ты имел в виду :о)
Да, это не то что я имел в виду. Честно говоря я даже не знаю про какую-то особую роль Attribute в ComponentModel :)
Кстати, по поводу WPF, заметил что на форумах на вопросы по WPF заметно меньше ответов. Имхо это очень важный фактор в переходе на WPF. Так вот возникнет какая-нибудь нетривиальная проблема и фиг решение найдешь если что.
ОтветитьУдалитьТак он же эти сложные вопросы и задаёт.
ОтветитьУдалить