воскресенье, 2 сентября 2007 г.

Нетрадиционное использование using и IDisposable

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

private void button1_Click(object sender, EventArgs e)
{
Cursor oldCursor = Cursor.Current;
try
{
Cursor.Current = Cursors.WaitCursor;
// do something time consuming
DoSomethingTimeConsuming();
}
finally
{
Cursor.Current = oldCursor;
}
}

Хочу обратить внимание, что конструкция try finally необходима, что бы в случае необработанного исключения в DoSomethingTimeConsuming все равно восстановить курсор. Однако у данного обработчика есть несколько недостатков:

  • конструкция достаточно объемна
  • необходимость блока try finally не очевидна и поэтому блок можно забыть.
  • данный код приходится дублировать для каждого подобного обработчика.

У меня есть небольшой трюк, который выполняет ту же работу но не содержит выше перечисленные недостатки. Новый вариант обработчика будет выглядеть следующим образом:

private void button2_Click(object sender, EventArgs e)
{
using (WaitCursor wch = new WaitCursor())
{
// do something time consuming
DoSomethingTimeConsuming();
}
}

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

Код для класса WaitCursor:
internal class WaitCursor: IDisposable
{
private Cursor m_OldCursor;

internal WaitCursor()
{
m_OldCursor = Cursor.Current;
Cursor.Current = Cursors.WaitCursor;
}

public void Dispose()
{
Cursor.Current = m_OldCursor;
}
}

Использование using приводит к тому что блок try finally будет сгенерирован автоматически. Так же автоматически конструкцией using будет вызван метод WaitCursor. Dispose, который и восстановит курсор.
Хочу отметить, что подобный механизм можно использовать не только для управления курсором.

6 комментариев:

Анонимный комментирует...

А еще лучше тип курсора передавать как параметр конструктора( если будут использоваться не только песочные часы )

Андрей Бороздин комментирует...

Как правило, такое поведение не требуется и резервировать заранее конструктор излишне. Тем более что придется сделать имя класса более общим и как следствие менее понятным.

Анонимный комментирует...

В качестве параметра в конструкторе класса WaitCursor надо передать Control, курсор которого меняется.
Использование статик свойства Cursor.Current не всегда оправдано.

Андрей Бороздин комментирует...

Не согласен. Если длительная операция временно блокирует работу главного потока приложения, то приложение целиком временно перестанет отвечать на действия пользователя. О чем и будет сигнализировать изменение статического свойства Cursor.Current. Изменение текущего курсора только одного контрола, будет создавать у пользователя иллюзию, что остальные контролы могут обрабатывать его команды, хотя в действительности это будет не так.

Анонимный комментирует...

Дай Бог памяти.
В CLR via C# было это описано

Андрей Бороздин комментирует...

to C...R...a...S...H
что именно?