3

I have a class that raises an event. I want the subscriber to be able to modify the values being passed in the EventArgs.

In the class that raises the events:

    class Factory
    {
    public event EventHandler<MessageReceivedEventArgs> MessageReceived;

    private IServerLib _myObject;

    public void Connect()
    {
        _myObject = new ServerLib();
        _myObject.AddMessageReceivedHandler((short terminal, ref string message, ref short functionNo) =>
        {
            MessageReceivedEventArgs args = new MessageReceivedEventArgs { Terminal = terminal, Message = message, FunctionNo = functionNo };
            MessageReceivedEvent(ref args);
        });
    }

    private void MessageReceivedEvent(ref MessageReceivedEventArgs args)
    {
        EventHandler<MessageReceivedEventArgs> handler = MessageReceived;
        if (handler != null)
        {
            handler(this, args);
        }
    }

    public class MessageReceivedEventArgs : EventArgs
    {
        public short Terminal { get; set; }
        public string Message { get; set; }
        public short FunctionNo { get; set; }
    }
}


interface IServerLib
    {
        void AddMessageReceivedHandler(MessageReceivedEventHandler action);
    }
    public delegate void MessageReceivedEventHandler(short terminal, ref string message, ref short functionNo);

The subscriber (which happens to be VB) looks like this:

Dim WithEvents _va As MyAssembly.MyClass

Private Sub _va_MessageReceived(sender As Object, e As Factory.MessageReceivedEventArgs) Handles _va.MessageReceived
    Debug.WriteLine($"Message: {e.Message} Terminal: {e.Terminal} Function: {e.FunctionNo}")
    If e.Message = "1" Then
        e.Message = ""
        e.FunctionNo = 0
        Debug.WriteLine("Cancelled")
    End If
End Sub

This raises the event, but setting the e.Message and e.Function do not seem to set the values. Am I doing something wrong?

Matt Wilko
  • 26,994
  • 10
  • 93
  • 143
  • If you think that your "ref" parameters will be affected then yes - you are doing something wrong, they won't. Instead pass instance of MessageReceivedEventArgs to your MessageReceivedEvent. – Evk Jun 09 '16 at 13:36
  • @Evk - I believe I am doing that `handler(this, args);` `args` is an instance – Matt Wilko Jun 09 '16 at 13:39
  • You can show code that actually compiles? (Since `(short x, ref string y, ref short z) =>` isn't allowed, this code seems flawed. – Patrick Hofman Jun 09 '16 at 13:39
  • @PatrickHofman - I will try to but I assure you that line is allowed as it compiles on my machine – Matt Wilko Jun 09 '16 at 13:57
  • Can you show `AddMessageReceivedHandler`? It gets a delegate of course... – Patrick Hofman Jun 09 '16 at 13:58

3 Answers3

3

The problem lies in the use of this line:

var args = new MessageReceivedEventArgs
           { Terminal = terminal, Message = message, FunctionNo = functionNo };

It copiex all variables to the event args class. Changing it there doesn't automatically change it on the other end (where you added ref). Not a nice solution, but to proof to you this is the issue, add this after handler(this, args):

message = args.Message;
functionNo = args.FunctionNo;

This will cause the refs to overwrite the values.

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • Ah yes - I see now. Do you know of any "nice" solution. This sort of thing is done by the framework in `CancelEventArgs` of FormClosing for example - do you know how that might be done? – Matt Wilko Jun 09 '16 at 14:11
  • Yes, and there they use the value inside the receiving method. The result is not passed outside the method boundaries (and if so, they use a return value probably) – Patrick Hofman Jun 09 '16 at 14:12
  • This is actually a decent solution. You could pass in one object, instead of passing in values by reference. – Patrick Hofman Jun 09 '16 at 14:13
1

It seems you think that your "ref" parameters should be affected by the code above, but that is not true. Yes you pass your message by reference to MessageReceivedEvent function, but then you assign it to MessageReceivedEventArgs.Message, and this happens by value, not by reference.

In result, when you modify MessageReceivedEventArgs.Message in your VB code - message variable is not affected (but MessageReceivedEventArgs.Message is affected of course), despite that you passed it by reference, as it should be.

What you should do instead is passing instance of MessageReceivedEventArgs to your function directly (not creating it inside that function):

private void MessageReceivedEvent(MessageReceivedEventArgs args)
{
    EventHandler<MessageReceivedEventArgs> handler = MessageReceived;
    if (handler != null)
    {
        handler(this, args);
    }
}
Evk
  • 98,527
  • 8
  • 141
  • 191
  • This still doesn't seem to work. I have now modified the argument as you suggested and created an instance of the MessageReceivedEventArgs in the Connect Method and passed this. – Matt Wilko Jun 09 '16 at 13:57
  • It should work, if It doesn't please update your question with new code (and clarify your goals). – Evk Jun 09 '16 at 14:00
  • 1
    Passing `args` doesn't help, since it still doesn't copy the `ref`s. – Patrick Hofman Jun 09 '16 at 14:10
  • @PatrickHofman I meant completely different thing - get rid of all refs and use one instance of a agrs. Actually what you propose in comments to your answer. But after update to the question I see its not possible, because refs are in event signature. – Evk Jun 09 '16 at 14:34
  • Okay, I understand. Your answer pointed in the right direction though @Evk – Patrick Hofman Jun 09 '16 at 15:07
0

First off, you don't need ref in order to change a property of your MessageReceivedEventArgs. The object variable is just a pointer to the real object in memory so you already changing its properties. If you need your subscriber to change the reference itself - i.e. to reset the pointer to another object - then you need to use ref. Second, this design is terrible thus I'm not sure I understand the problem. Anyway I'm going to provide a proper way to raise events and consume the changed values after that.

class Factory 
{
    public event EventHandler<MessagereceivedEventArgs> MessageReceived;

    void ReceiveMessage(string Message)
    {
        // Do something with the Message

        // Then let your subscribers know that the message has been processed:
        if (MessageReceived != null)
        {
            var ea = new MessageReceivedEventArgs();
            ea.Message = Message;
            // Set ea properties as appropriate 
            MessageReceived(this, ea);
            // Check ea properties for change
            if (ea.Message != Message)
            {
                // A subscriber has changed the message in the MessageReceivedEventArgs
            }
        }
    }
}
Bozhidar Stoyneff
  • 3,576
  • 1
  • 18
  • 28