Search code examples
c#.netvb.neteventscode-translation

Convert C# to VB,NET: Event with a delegate type that has a return type


This is the original source-code written in C#

public delegate Unit UnitResolveEventHandler(object sender, ResolveEventArgs args);

public event UnitResolveEventHandler UnitResolve;

public static Unit GetUnitByName(string name) {
    Instance.unitsByName.TryGetValue(name, out result);
    if (Instance.UnitResolve != null) {
        foreach (UnitResolveEventHandler handler in Instance.UnitResolve.GetInvocationList()) {
            result = handler(Instance, new ResolveEventArgs(name));
        }
    }
}

Using an online translator, I get this VB.NET code:

Public Delegate Function UnitResolveEventHandler(sender As Object, args As ResolveEventArgs) As Unit

Public Event UnitResolve As UnitResolveEventHandler

Public Shared Function GetUnitByName(name As String) As Unit
    Instance.unitsByName.TryGetValue(name, result)
    If Instance.UnitResolve IsNot Nothing Then
        For Each handler As UnitResolveEventHandler In Instance.UnitResolve.GetInvocationList()
            result = handler(Instance, New ResolveEventArgs(name))
        Next
    End If
End Function

The compiler marks the event declaration with this error message:

Events cannot be declared with a delegate type that has a return type.

And the Instance.UnitResolve calls inside the GetUnitByName() method with this error message:

Public Event UnitResolve As UnitResolveEventHandler' is an event, and cannot be called directly.

How can I properly translate the code from C# to VB.NET without losing functionality?


Solution

  • The customary way of returning a value from an event handler to the invocation of the event is through an argument---either a member of the event arguments class, or through a ByRef parameter on the delegate.

    If you have control over ResolveEventArgs and the event handler routines, you could do something like this:

    Public Class ResolveEventArgs
        '...
        Public ReturnValue As Unit
        '...
    End Class
    

    In the body of your handler (assuming the typical declaration of the event arguments as e), instead of Return (return value):

    e.ReturnValue = (return value) 'substitute for (return value) as appropriate
    

    Then, the body of your For Each loop would look like this:

    Dim args As New ResolveEventArgs(name)
    handler(Instance, args)
    result = args.ReturnValue
    

    As an aside, the original C# code has a thread-safety issue. It could throw a NullReferenceException in the event that the last subscribed handler is removed between the null check and reading the invocation list. Whether this is serious (or a concern at all) depends on where and how it's being used. The usual way of addressing this is to store to a temporary, then do the null check and invocation list on the temporary. If you're using a recent version of the .NET languages, then you can skip the null check and use the ?. operator, which should also be safe against the particular thread-safety issue.