0

I wrote this code (simplified):

Camera cam;
cam.StreamGrabber.ImageGrabbed += (_, e) => StreamGrabber_ImageGrabbed(cam, e);

The Camera class is imported from a compulsory third-party dll that I cannot modify. For some reason the ImageGrabbed event does not specify which camera it is being grabbed from (there are multiple cameras). I need to reference the camera in the anonymous method, but now I cannot find a way to unsubscribe from the event.

When searched online they mostly boil down to:

Action myDelegate = delegate(cam, e)
{ 
    //do something; 
};
cam.StreamGrabber.ImageGrabbed += myDelegate;
cam.StreamGrabber.ImageGrabbed -= myDelegate;

but those don't work because I need to reference the cam and not the the ImageGrabbed parameters.

Please do advice me on some workarounds especially if it isn't possible.

EDIT: It seems I have oversimplified my code and missed some important details, here's a less simplified version

public class MyInterface() : IFrameGrabber
{
    private var _cameras = new List<Camera>();
    public void AddCamera()
    {
         var cam = new Camera();
         cam.StreamGrabber.ImageGrabbed += (_, e) => StreamGrabber_ImageGrabbed(cam, e);
         cam.OnOpened += Camera_OnOpened;
         _cameras.Add(cam);
    }
    
    public void StopCamera(int id) // Just stopping not removing
    {
         // Unsubscribe only the ImageGrabbed event from _cameras[id] here
    }
}
         

EDIT2: The code for the event

public void StreamGrabber_ImageGrabbed(Camera sender, ImageGrabbedEventArgs e)
{
    var timer = Stopwatch.StartNew();

    var serialNumber = sender.CameraInfo["SerialNumber"];
    var result = e.GrabResult;
    if (!result.GrabSucceeded) return;

    var stride = result.ComputeStride();
    if (stride == null) return;

    _grabCount[serialNumber]++;
    var pixelType = e.GrabResult.PixelTypeValue switch
    {
        PixelType.Mono8 => MatType.CV_8UC1,
        _ => MatType.CV_8UC1,
    }; // TODO
    ImagesGrabbed?.Invoke(new(this, serialNumber, result.PixelDataPointer, result.Width, result.Height,
        (int)stride, pixelType, timer.ElapsedTicks / Stopwatch.Frequency)); 
}

Decompiled dll code

event EventHandler<ImageGrabbedEventArgs> ImageGrabbed;

&

public class ImageGrabbedEventArgs : EventArgs
{
    private IGrabResult m_grabResult;

    private bool m_isClone;

    public bool IsClone
    {
        [return: MarshalAs(UnmanagedType.U1)]
        get
        {
            return m_isClone;
        }
    }

    public IGrabResult GrabResult => m_grabResult;

    public ImageGrabbedEventArgs(IGrabResult grabResult)
    {
        m_grabResult = grabResult;
        m_isClone = false;
        base._002Ector();
    }

    public ImageGrabbedEventArgs Clone()
    {
        ImageGrabbedEventArgs imageGrabbedEventArgs = new ImageGrabbedEventArgs(m_grabResult.Clone());
        imageGrabbedEventArgs.m_isClone = true;
        return imageGrabbedEventArgs;
    }

    public void DisposeGrabResultIfClone()
    {
        if (IsClone)
        {
            m_grabResult?.Dispose();
        }
    }
}
11
  • Where is cam declared? Is it a class field? Commented Jun 24 at 5:37
  • Personally, for me a.b.c += ... is already a red flag. How much power do you have on cam's implementation? Can you forward internally, so you can register an eventhandler on cam, directly? Commented Jun 24 at 5:40
  • ^^ See, for a class called Camera, I'd expect the consumer to not be able to see that it has a Streamgrabber, that has an ImageGrabbed event. That's in implementation detail. I'd expect to get images from the Camera. Commented Jun 24 at 5:41
  • 1
    I didn't get your real problem so far. Generally you cannot unsubscribe an anonymous function, you need to have a reference to the function stored in a variable and unsubscribe that. But it seems there's some second problem which we cannot determine as there's too less information about it. Can you show what the ImageGrabbed-event looks like and what StreamGrabber_ImageGrabbed is? Commented Jun 24 at 6:12
  • 1
    @Fildor haha don't worry that isn't the actual name of my interface, just didn't bother writing the actual name for the example Commented Jun 25 at 1:07

3 Answers 3

4

Maybe you can wrap that Camera-class in order to handle the object's lifetime appropriately.

class MyCamera(Camera cam)
{
    void DoSomethingThatRaisesTheEvenet()
    {
        this.ImageGrabbed?.Invoke(this.cam, myEventArgs);
    }

    public event EventHandler<ImageGrabbedEventArgs> ImageGrabbed;
}

In your calling code you can do this:

var cam = new MyCamera(new Camera()); // create the wrapper-instance here
cam.ImageGrabbed += (c, e) => myEventHandler;
     

with

void myEventHandler(object sender, ImageGrabbedEventArgs e) 
{
    var cam = (Camera) sender; // might throw InvalidCastException
    ...
}

As your wrapper-class has a reference to the Camera-object, you can freely pass it to the event. However your client-code doesn't need to know anything about that object. Alternativly pass your MyCamera-instance to the eventhandler. However in that case you need to expose the wrapped Camera-instance from the wrapper-class.

Sign up to request clarification or add additional context in comments.

4 Comments

It seems there is also the requirement to "switch grabbing off and on". Using a wrapper like this, we can do that, too. (That's what I mean => public void StopCamera(int id) // Just stopping not removing )
didn't even notice that requirement in the question, but yes, you're right. The wrapper can handle that.
so OP needs to maintain a List<MyCamera> on top of List<Camera> or do we need to expose a property Camera Instance?
suely not both - a List<MyCamera> and a List<Camera>. But we don't know for sure as it's unclear how and where cameras are created.
2

You're capturing cam in the lambda, this ties the delegate to the specific Camera instance, but you lose the ability to unsubscribe unless you store the delegate, try storing the delegate in a variable that captures the cam

Something like this:

// Store the delegate with cam captured
EventHandler<ImageGrabbedEventArgs> handler = (sender, e) => StreamGrabber_ImageGrabbed(cam, e);

// Subscribe
cam.StreamGrabber.ImageGrabbed += handler;

// Later, unsubscribe
cam.StreamGrabber.ImageGrabbed -= handler;

3 Comments

This doesn't work, this is the same ballpark of what my online search example has shown. The "cam" in StreamGrabber_ImageGrabbed(cam, e); will not have been defined yet until the moment it is subscribed to. You can test it, and it will say "'cam' does not exist in the current context".
Ah, I should probably specify that I will be needing to unsubscribe from multiple different functions, so I will need to be unsubscribing from outside this scope.
In that case, you maybe want to consider going reactive rather than using events. That way, you can advise Camera to end the image stream and all subscribers will actively be cleared and disposed.
1

We can just use a Dictionary<Camera, EventHandler<ImageGrabbedEventArgs>>:

public class MyInterface() : IFrameGrabber
{
    private var _cameras = new List<Camera>();
    private Dictionary<Camera, EventHandler<ImageGrabbedEventArgs>> _cameraImageGrabbed = new Dictionary<Camera, EventHandler<ImageGrabbedEventArgs>>();

    public void AddCamera()
    {
         var cam = new Camera();
         var del = (_, e) => StreamGrabber_ImageGrabbed(cam, e);
         cam.StreamGrabber.ImageGrabbed += del ;
         _cameraImageGrabbed[car]=del;
         cam.OnOpened += Camera_OnOpened;
         _cameras.Add(cam);
    }
    
    public void StopCamera(int id) // Just stopping not removing
    {
         // Unsubscribe only the ImageGrabbed event from _cameras[id] here
         var cam = cameras[id];
         cam.StreamGrabber.ImageGrabbed -= _cameraImageGrabbed[cam];
    }
}

Still, this Dictionary-based logic can be abstracted a bit.

Instead of

cam.StreamGrabber.ImageGrabbed += (object _, ImageGrabbedEventArgs e) => StreamGrabber_ImageGrabbed(cam, e);

we could write

cam.StreamGrabber.ImageGrabbed += cam.AssociateWithEventHandler(
    (object _, ImageGrabbedEventArgs e) => StreamGrabber_ImageGrabbed(cam, e));

and then the tricky part of unsubscribing would become:

cam.StreamGrabber.ImageGrabbed -= cam.DisassociateFromEventHandler<Camera,
    ImageGrabbedEventArgs>();

This could be done with the help of Expressions, extension methods that work with EventHandler<TEventArgs> events/delegates and some static private Dictionaries for holding the actual mapping. The following is an implementation of those methods that is relying on

  • the use of local captured variables, i.e. you can't use cameras[0], but you should first do var cam = cameras[0]
  • there being a method call StreamGrabber_ImageGrabbed(cam, e)) - can be static or instance, but can't work with multiline lambda implementation in its place.

Code:

public static class LambdaCapturedVariableEventHandlerExtensions {

    public static EventHandler<TEventArgs> AssociateWithEventHandler<T, TEventArgs>(
        this T instance, Expression<EventHandler<TEventArgs>> expression,
        string name = nameof(TEventArgs))
        where T : class {
        
        ArgumentNullException.ThrowIfNull(nameof(instance));

        var formatExpressionExceptionMessage = "Expression should be in the form (object _, TEventArgs e) => Method(instance, e) where \"instance\" is local captured variable";
        if (expression.Body is not MethodCallExpression methodCallExpression)
            throw new InvalidOperationException(formatExpressionExceptionMessage);

        var memberExpressions = methodCallExpression
              .Arguments
              .Where(x => x.Type == typeof(T))
              .OfType<MemberExpression>();

        if (memberExpressions.Count() != 1)
            throw new InvalidOperationException(formatExpressionExceptionMessage);

        var memberExpr = memberExpressions.First();
        if (memberExpr.Member is not FieldInfo fieldInfo)
            throw new InvalidOperationException(formatExpressionExceptionMessage);
        if (memberExpr.Expression is not ConstantExpression constExpr)
            throw new InvalidOperationException(formatExpressionExceptionMessage);

        var closure = constExpr.Value;
        T camValue = (T)fieldInfo.GetValue(closure);
        if (camValue != instance)
            throw new InvalidOperationException("You need to use the same instance in the expression");

        var key = (instance, name);
        if (EventHandlerAssociationHelper<T, TEventArgs>.s_Dictionary.ContainsKey(key))
            throw new InvalidOperationException($"A combination of {name} and {instance.ToString()} already exists");

        var del = expression.Compile();
        EventHandlerAssociationHelper<T, TEventArgs>.s_Dictionary[(instance, name)] = del;
        return del;
    }

    public static EventHandler<TEventArgs> DisassociateFromEventHandler<T, TEventArgs>
        (this T instance, string name = nameof(TEventArgs)) {

        ArgumentNullException.ThrowIfNull(nameof(instance));

        if (!EventHandlerAssociationHelper<T, TEventArgs>.s_Dictionary
            .TryGetValue((instance, name), out var result)) {
            throw new InvalidOperationException();
        }

        return result;
    }

    // TODO: Thread-safety
    private static class EventHandlerAssociationHelper<T, TEventArgs> {
        public static Dictionary<(T, string), EventHandler<TEventArgs>> s_Dictionary =
            new();
    }
}

Then use with some dummy implementations for our classes would be:

var camInstance = new Camera() {
    StreamGrabber = new StreamGrabber()
};

var cameras = new List<Camera> { camInstance };
var cam = cameras[0]; // has to be a local variable to work
                      // i.e. following won't work
                      // cam.StreamGrabber.ImageGrabbed += cameras[0].AssociateWithEventHandler(
                      //    (object _, ImageGrabbedEventArgs e) => StreamGrabber_ImageGrabbed(cameras[0], e));

// this will
cam.StreamGrabber.ImageGrabbed += cam.AssociateWithEventHandler(
    (object _, ImageGrabbedEventArgs e) => StreamGrabber_ImageGrabbed(cam, e));

cam.StreamGrabber.InvokeImageGrabbed(42);

// try associating another event handler with same type ImageGrabbedEventArgs
try {
    cam.StreamGrabber.SomethingElse += cam.AssociateWithEventHandler(
   (object _, ImageGrabbedEventArgs e) => StreamGrabber_SomethingElse(cam, e));

} catch (Exception ex) {
    Console.WriteLine(ex.Message);
}

cam.StreamGrabber.SomethingElse += cam.AssociateWithEventHandler(
(object _, ImageGrabbedEventArgs e) => StreamGrabber_SomethingElse(cam, e),
"SomethingElseNamed");

cam.StreamGrabber.ImageGrabbed -= cam.DisassociateFromEventHandler<Camera,
    ImageGrabbedEventArgs>();
Console.WriteLine("---------------------");
cam.StreamGrabber.InvokeImageGrabbed(42222);
Console.WriteLine("nothing");
Console.WriteLine("---------------------");
cam.StreamGrabber.InvokeImageSomethingElse(42222);
// this is not using the name, but a no-op, returning null
cam.StreamGrabber.SomethingElse -= cam.DisassociateFromEventHandler<Camera,
  ImageGrabbedEventArgs>();
cam.StreamGrabber.InvokeImageSomethingElse(42222);
cam.StreamGrabber.SomethingElse -= cam.DisassociateFromEventHandler<Camera,
   ImageGrabbedEventArgs>("SomethingElseNamed"); // need to use the name
Console.WriteLine("---------------------");
cam.StreamGrabber.InvokeImageSomethingElse(42222);
Console.WriteLine("nothing");

You can notice, that in case there are multiple events with the same EventArgs Type, i.e. ImageGrabbedEventArgs (see DotnetFiddle with fully runnable example), you can use the name argument to disambiguate.

4 Comments

Will probably work. Although, personally, I'd prefer a wrapper to encapsulate all that logic.
yeah, just posting this because to me that's the most obvious solution and didn't see it mentioned so far
@Fildor opinion about the extension methods ?
Looks fine to me.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.