1

I have a server that receives files. After receiving file, an event is invoked. It works something like this:

public void Receive() {
    // do some file receiving 
    // decrypt received file
    // save file

    // when file is received decrypted and saved, invoke event:   
    OnFileReceived?.Invoke(this, fileName);

}

...

public static event EventHandler<string>? OnFileReceived;


I subscribe to this event in constructor of other class so it fires up a method that opens a file explorer. There is only one instance of that class, so I'm quite sure that event should be invoked only once.

public Foo {
   // constructing object
   // subscribing to events:
   Server.OnFileReceived -= OnFileDownloaded;
   Server.OnFileReceived += OnFileDownloaded;
}

...

 private void OnFileDownloaded(object? sender, string filename)
        {
            InfoLabel = "Received: " + filename;
            OpenDirectory();
        }

The problem is that file explorer is opened twice. I did a little investigation, and it turns out that for some reason my event is being invoked twice in Receive() method. And it drives me nuts.

I tried to fix that first by adding a simple boolean to OnFileDownloaded method:

private void OnFileDownloaded(object? sender, string filename)
        {
            if (!_isInvoked)
            {
                _isInvoked = true;
                InfoLabel = "Received: " + filename;
                OpenDirectory(); // then setting here _isInvoked back to false after closing the File explorer
            }
        }

But It did not work. I also have tried a solutions found here and here, changing the event declaration:

private EventHandler<string> fileReceived;
public event EventHandler<string> OnFileReceived
{
    add
    {
        if (fileReceived == null || !fileReceived.GetInvocationList().Contains(value))
        {
            fileReceived += value;
        }
    }
    remove
    {
        fileReceived -= value;
    }
}

Again, with no luck. The question is: How can I prevent this from happening?

Thanks.

0tto
  • 47
  • 1
  • 9
  • 1
    `Contains` is going to do a reference test but `value` may be a different instance with the same `Target` and `Method` values. Have to test those instead. – madreflection Dec 16 '21 at 17:33
  • 1
    Are you sure there is only ever one instance of `Foo`? If so why would you need `Server.OnFileReceived -= OnFileDownloaded;`? And if there is more than one instance, that code won't work as it refers to a different instance's method. I suggest you set a breakpoint in the constructor of `Foo` and see how many times it was hit. What happens if you make `OnFileDownloaded` a `static` function? – Charlieface Dec 16 '21 at 23:10
  • @Charlieface I'm sure cuz Foo is a ViewModel and there can be just one instance of ViewModel per View. I put `Server.OnFileReceived -= OnFileDownloaded;` in constructor of the Foo because I found such suggestion in links mentioned in question - but regardless with or without it, it just does not work. Also I cannot make the `OnFileDownloaded` static because it contains some non static methods inside it, which also cannot be made static. – 0tto Dec 17 '21 at 12:36
  • @madreflection Im not sure if I understand. How can a value be a different instance if there is one and only one instance of Foo class? – 0tto Dec 17 '21 at 12:49
  • Not "a" value, but *the* value of the `value` parameter in the `add` accessor. When you do `Server.OnFileReceived -= OnFileDownloaded;`, the right side, `OnFileDownloaded` is actually `new EventHandler(OnFileDownloaded)` (prior to C# 2.0, you had to do it that way, with `new`). That's a different *instance* of `EventHandler` than when you did `Server.OnFileReceived += OnFileDownloaded;`. In the `add` accessor, `value` has a different instance than the one you'll find in the invocation list when you call `fileReceived.GetInvocationList()`. – madreflection Dec 17 '21 at 15:45
  • I'm sorry it seems like you were both right @madreflection @Charlieface. I just got all values from `fileReceived.GetInvocationList()` into log file and it turns out that Foo's constructor is **somehow** called TWICE - and thus the event is subscribed twice. I have no idea how the hell did I miss that. Thank you anyway. Also @madreflection thank you for explanation, it did help me a lot. – 0tto Dec 17 '21 at 15:56
  • So you can't just do `Contains(value)` like that. You need to look for an instance with matching `Method` and `Target` properties, something like `.Where(inv => inv.Method == value.Method && inv.Target == value.Target).Any()`. And that assumes that `value` itself doesn't have multiple invocations. If it does, those properties only represent *one* of the invocations in the list. It shouldn't based on how you're using it, but in the mindset of the implementor of the event, you have to know that it could happen. – madreflection Dec 17 '21 at 15:56
  • You're welcome. I had to rewrite the second comment because I realized a mistake after the edit cut-off. I added some details while I was at it. Unfortunately, it looks out of order now. – madreflection Dec 17 '21 at 15:57

1 Answers1

3

As gentlemen in comments suggested, my problems were caused by calling Foo's constructor twice, which then resulted in two separate objects of Foo. And since the event is subscribed in mentioned constructors... I got one additional subscriber.

I was pretty sure that this could not be the case, because I searched through the code looking for another call Foo's constructor: new Foo(), and found nothing. My fatal mistake was to assume that I was coherent with the way I call the constructor...

And then somewhere deep in the code I found this lonely line:

private Foo? _viewModel = new();

Too much sugar can make your teeth go bad, too much syntax sugar can make you go mad. Lesson learned: be coherent.

0tto
  • 47
  • 1
  • 9
  • Please have a look at [Can I answer my own question?](http://stackoverflow.com/help/self-answer) and come back two days later and check as answered if you have more than 15 reputation (See also: [Accept Your Own Answers](https://stackoverflow.blog/2009/01/06/accept-your-own-answers/). – help-info.de Dec 17 '21 at 16:43