Подписка на нужный делегат в зависимости от переданного параметра

Надеюсь, корректно отобразил проблему в заголовке. Дайте знать, если нужно переписать её.

Возник вопрос в правильности моего кода. Суть в том, что у меня существует 2 метода - синхронный и синхронный, который под капотом делит List на несколько и выполняет обработку в нескольких потоках, который задаётся в конструкторе в зависимости от переданных параметров.

private delegate Image FaceMethodDelegate(object sender, NewFrameEventArgs e);
private FaceMethodDelegate_syncFaceDelegate;
private FaceMethodDelegate_asyncFaceDelegate;

public Image CameraImage { get; private set; }

public DetectFace(MethodChoiceEnum methodType)
{
    _syncFaceDelegate = Device_NewFrame;
    _asyncFaceDelegate = Device_NewFrame_Parallel;

    _device.NewFrame += (sender, e) =>
    {
        CameraImage = methodType is MethodChoiceEnum.Sync
            ? _syncFaceDelegate(sender, e)
            : _asyncFaceDelegate(sender, e);
    };
}

Далее через метод Start запускается нужный метод.

public void Start() =>
    _device.Start();

Используются библиотеки EmguCv + AForge.

Собственно, ещё раз вопрос - является ли корректным данный выбор метода или стоит как-то переписать данных код? Оба метода возвращают Image

UPD:

_syncFaceDelegate:

private Image Device_NewFrame(object sender, NewFrameEventArgs e)
{
    foreach (var f in facesDetectedNow.First())
    {
        if (**)
        {
            //somecode
            break;
        }
    }
}

_asyncFaceDelegate:

private Image Device_NewFrame_Parallel(object sender, NewFrameEventArgs e)
{
    Parallel.ForEach(facesDetectedNow[0], (f, state) =>
        if (**)
        {
            //somecode
            state.Stop();
        });
}

Ответы (1 шт):

Автор решения: Pavel Mayorov

Ваш способ корректный (при условии, что оба метода и правда возвращают Image), но странный.

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

        CameraImage = methodType is MethodChoiceEnum.Sync
            ? Device_NewFrame(sender, e)
            : Device_NewFrame_Parallel(sender, e);

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

   switch (methodType) {
       case MethodChoiceEnum.Sync:
           _device.NewFrame += Device_NewFrame;
           break;

       case MethodChoiceEnum.Parallel:
           _device.NewFrame += Device_NewFrame_Parallel;
           break;
   }

PS меня настораживает название вашего класса - такое ощущение что вы его сделали там, где было бы достаточно метода. Этот класс мог бы быть частью ViewModel, если бы у него было событие CameraImageChanged или реализация INotifyPropertyChanged, но я их не вижу. Возможно, вы просто выкинули лишние детали чтобы получить MCVE, в таком случае всё в порядке. Но если он и правда такой небольшой, то надо с ним что-то делать.

Если смысл класса лишь в выборе между двумя функциями, то вместо него можно было бы использовать статический метод:

static FaceMethodDelegate SelectFaceImageProcessor(MethodChoiceEnum methodType) {
   switch (methodType) {
       case MethodChoiceEnum.Sync:
           return Device_NewFrame;

       case MethodChoiceEnum.Parallel:
           return Device_NewFrame_Parallel;

       default:
           throw …;
   }
}

Если же смысл класса в том, чтобы быть однократным контейнером для CameraImage - его можно заменить на метод, который вернёт Task:

static async Task<Image> CaptureImageAsync (… device, MethodChoiceEnum methodType) {
   var tcsEventArgs = new TaskCompletionSource<NewFrameEventArgs>();

   SomeEventHandler handler = (o, e) => tcsEventArgs.SetResult(e);
   device.NewImage += handler;
   var eventArgs = await tcsEventArgs;
   device.NewImage -= handler;

   switch (methodType) {
       case MethodChoiceEnum.Sync:
           return Device_NewFrame(eventArgs);

       case MethodChoiceEnum.Parallel:
           return Device_NewFrame_Parallel(eventArgs);

       default:
           throw …;
   }
}

Для случая же обновляемого контейнера (который можно уже считать ViewModel) явно нужно событие обновления картинки:

private Image _cameraImage;
public Image CameraImage { 
  get => _cameraImage;
  private set {
    _cameraImage = value;
    CameraImageChanged?.Invoke(this, EventArgs.Empty);
  }
}
public event EventHandler CameraImageChanged;


→ Ссылка