Проблема
Рассмотрим следующий код:
class A
{
private int _workId;
public void DoWork()
{
_workId = new Random().Next(100);
Console.WriteLine("Starting a long process ID: " + _workId);
LongProcess longProcess = new LongProcess();
longProcess.StartWork();
longProcess.OnWorkMiddle += OnLongProcessWorkMiddle;
Console.WriteLine("At end of long process: " + _workId);
longProcess.OnWorkMiddle -= OnLongProcessWorkMiddle;
}
private void OnLongProcessWorkMiddle(object sender, EventArgs e)
{
Console.WriteLine("In middle of long process: " + _workId);
}
}
Следующий код проблематичен на мой взгляд по нескольким причинам:
- Существуетразделение кода, которое по логике должно быть сгруппировано вместе - я ожидал бы, что
Console.WriteLine("In middle of long process: " + _workId);
появится рядом с Console.WriteLine("At end of long process: " + _workId);
, и, тем не менее, они оба появляются в отдельных функциях.функция внутри DoWork , однако я вынужден сделать ее полевой функцией, чтобы иметь возможность доступа к этой информации изнутри обратного вызова.Каждый дополнительный параметр или возвращаемое значение также станет элементом поля.
Альтернативные решения
Существует несколько решений, которые могут уменьшить количество проблем в этом коде:
Инкапсуляция функции и ее обратного вызова в другом классе
Одной из проблем, которые я описал выше, были члены поля, которые не обязательно связаны с классом владельца в целом,но вместо этого специфичен для функции DoWork и ее обратных вызовов.Инкапсулируя функцию в своем собственном классе, мы удаляем этот ненужный беспорядок и группируем его в свою логическую группу.Недостатком является то, что он требует создания класса из того, что по заслугам должно было быть функцией.Это также не решает проблему № 1.
Использование анонимных делегатов для сохранения в одной функции
Мы могли бы переписать код следующим образом:
public void DoWork()
{
var workId = new Random().Next(100);
EventHandler onLongProcessWorkMiddle = delegate { Console.WriteLine("In middle of long process: " + workId); };
Console.WriteLine("Starting a long process ID: " + workId);
LongProcess longProcess = new LongProcess();
longProcess.StartWork();
longProcess.OnWorkMiddle += onLongProcessWorkMiddle;
Console.WriteLine("At end of long process: " + workId);
longProcess.OnWorkMiddle -= onLongProcessWorkMiddle;
}
Теперь нам больше не нужны какие-либо члены поля, и мы можем все красиво хранить в одной функции.Тем не менее, с точки зрения читабельности я считаю, что в коде, скорее всего, отсутствует код, (или, скорее, его делегат), который выполняется позже, появляется сверху.
Использование полуобобщенного вспомогательного класса
В этом решении я написал полуобобщенный вспомогательный класс для синхронизации выполнения.Это позволяет мне написать код следующим образом:
public void DoWork()
{
var workId = new Random().Next(100);
LongProcess longProcess = new LongProcess();
var syncr = new EventSyncr();
syncr.Sync(
delegate { longProcess.OnWorkMiddle += syncr.EventCallback; },
delegate { longProcess.StartWork(); },
delegate { Console.WriteLine("In middle of long process: " + workId); },
delegate { longProcess.OnWorkMiddle -= syncr.EventCallback; });
Console.WriteLine("At end of long process: " + workId);
}
А вот код для вспомогательного класса:
/// <summary>
/// Used to synchronize code that waits for a callback from a function.
/// </summary>
class EventSyncr
{
readonly AutoResetEvent _eventCallbackEvent = new AutoResetEvent(false);
private Action _actionAtEvent;
/// <summary>
/// Executes the syncing process. This function is blocked until the event is called back (once), then it executes the 'actionAtEvent' segment and
/// after unsubscription, exists.
/// </summary>
/// <param name="eventSubscription">The passed delegate must subscribe the event to EventSyncr.EventCallback.</param>
/// <param name="eventCausingAction">The event causing action.</param>
/// <param name="actionAtEvent">The action at event.</param>
/// <param name="eventUnsubscription">Unsubscribe the event that was subscribed in the first parameter</param>
public void Sync(Action eventSubscription, Action eventCausingAction, Action actionAtEvent, Action eventUnsubscription)
{
_actionAtEvent = actionAtEvent;
try
{
eventSubscription();
eventCausingAction();
_eventCallbackEvent.WaitOne();
}
finally
{
eventUnsubscription();
}
}
public void EventCallback(object sender, EventArgs e)
{
try
{
_actionAtEvent();
}
finally
{
_eventCallbackEvent.Set();
}
}
}
Хотя немного на многословной стороне, это делаетмне кажется, что он решает все проблемы, которые я поднял выше:
Код выглядит так, как он будет выполняться, в его логическом порядке, и все дополнительные свойства являются самодостаточными.
Вопросы
Мне интересно, что вы думаете о проблеме и возможных решениях.Какой из них кажется лучшим?Есть ли лучший способ решения проблемы?
Я знаю, что в C # 5 функция await обеспечит гораздо лучшее решение, но мы еще не совсем там:(.
Спасибо!