3

Given the following delegate definition in C#:

struct Dummy {}
delegate int ReceiveDummy(in Dummy dummy);

How can I assign a a VB.Net sub to the ReceiveDummy type? Both the following function definitions fail with error BC30657: "has a return type that is not supported or parameter types that are not supported".

Shared Function MyReceiveDummy(ByVal dummy As Dummy) As Integer
Shared Function MyReceiveDummy(ByRef dummy As Dummy) As Integer
Dim receive As ReceiveDummy = New ReceiveDummy(AddressOf MyReceiveDummy)

Is there a VB.NET equivalent to C#'s "in" keyword?

Sjoerd van Kreel
  • 1,000
  • 6
  • 19
  • 1
    The direct match would be something like `Const ByRef` or `ReadOnly ByRef`, so it avoids needing to copy or duplicate the data while still ensuring the original is not changed: the best of both worlds from both performance and safety standpoints. I expect you understand that, because you knew enough to use `in` in the first place on the C# side. Unfortunately, I don't think VB.Net has anything like this. – Joel Coehoorn Jul 22 '23 at 16:38
  • That's too bad. Am I correct to think then, that the given delegate definition just plainly *cannot* be implemented in VB? Perhaps using some function-pointer-casting like tricks? I always thought c#'s in/out/ref are all just "ref" under the hood, with some additional compiler checks on the c# side for in/out. – Sjoerd van Kreel Jul 22 '23 at 16:42
  • @JoelCoehoorn could you make an answer out of your comment so I can accept it? – Sjoerd van Kreel Jul 22 '23 at 17:26
  • There's still hope someone chimes in and contradicts me on this. – Joel Coehoorn Jul 22 '23 at 20:07
  • I'll wait a bit, then. Thanks for your help. – Sjoerd van Kreel Jul 22 '23 at 20:46
  • In you vb.net code, can you at least refer to the **type** `ReceiveDummy`, e.g. does `GetType(ReceiveDummy)` compile in vb.net? Or can you declare a function that returns `ReceiveDummy` but just throws an exception and doesn't return anything? – dbc Jul 22 '23 at 21:54
  • @madreflection thanks for the clarification. That raises another question though, I also tried "[InAttribute] ByRef dummy as Dummy", but no luck. Does the VB compiler not process that attribute? – Sjoerd van Kreel Jul 22 '23 at 22:31
  • @dbc absolutely. I can declare variables of ReceiveDummy, pass objects of that type around, etc etc. I just can't cook up a VB.Net function which matches the signature. In case you're interested, this is the reason i asked in the first place: https://stackoverflow.com/questions/76724264/recording-audio-using-xtaudio-in-vb-net. – Sjoerd van Kreel Jul 22 '23 at 22:33
  • OK, then I have a **super hacky** way to do this. Want it? – dbc Jul 22 '23 at 22:34
  • @dbc I already recommended the person on the topic I just mentioned, to just write a small bit of c# wrapper code, which seems like the safe course of action. But, just for fun, I'd like to know :) – Sjoerd van Kreel Jul 22 '23 at 22:36

1 Answers1

3

This is extremely hacky, but it turns out to be possible (in .NET 7) to use Delegate.CreateDelegate() to create a delegate of a type with an in parameter from a static method whose signature actually accepts a ref parameter.

E.g. if I create (in C#):

struct Dummy {}
delegate int ReceiveDummy(in Dummy dummy);

static class DummyActions
{
    //https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.2/readonly-ref.md#metadata-representation-of-in-parameters
    static int ActuallyReceiveDummy(ref Dummy dummy)
    {
        return dummy.GetHashCode();
    }
    
    public static ReceiveDummy CreateReceiveDummy()
    {
        return (ReceiveDummy)
            Delegate.CreateDelegate(typeof(ReceiveDummy), 
                                    typeof(DummyActions).GetMethod(nameof(DummyActions.ActuallyReceiveDummy), 
                                                                   BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic));
    }
}

I can do:

var receiveMethod = DummyActions.CreateReceiveDummy();
Console.WriteLine(receiveMethod(new Dummy()));

An it works! Demo #1 here.

Translating to VB.NET, I get the following:

Friend Class DummyActions
    ' https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.2/readonly-ref.md#metadata-representation-of-in-parameters
    Private Shared Function ActuallyReceiveDummy(ByRef dummy As Dummy) As Integer
        ' Do whatever you want here but don't modify dummy!
        Return dummy.GetHashCode() 
    End Function

    Public Shared Function CreateReceiveDummy() As ReceiveDummy
        Return CType([Delegate].CreateDelegate(GetType(ReceiveDummy), 
            GetType(DummyActions).GetMethod(NameOf(DummyActions.ActuallyReceiveDummy), 
                BindingFlags.Static Or BindingFlags.Public Or BindingFlags.NonPublic)), ReceiveDummy)
    End Function
End Class

Which makes the following possible in VB.NET:

Dim receiveMethod = DummyActions.CreateReceiveDummy()
Console.WriteLine(receiveMethod(New Dummy()))

Mockup fiddle #2 here

Notes:

  • According to the design documents for in in C# 7.2

    When System.Runtime.CompilerServices.IsReadOnlyAttribute is applied to a byref parameter, it means that the parameter is an in parameter.

    In addition, if the method is abstract or virtual, then the signature of such parameters (and only such parameters) must have modreq[System.Runtime.InteropServices.InAttribute].

    Motivation: this is done to ensure that in a case of method overriding/implementing the in parameters match.

    Same requirements apply to Invoke methods in delegates.

    Motivation: this is to ensure that existing compilers cannot simply ignore readonly when creating or assigning delegates.

    It turns out that in C# the compiler will not allow you to manually apply IsReadOnlyAttribute, if you try you will get a compiler error Do not use 'System.Runtime.CompilerServices.IsReadOnlyAttribute'. This is reserved for compiler usage. (demo #3 here). But the VB.NET compiler makes no such restriction, so if you want to "future-proof" your method against some possible future version of Delegate.CreateDelegate() that checks for this attribute, you could add it manually:

    ' https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.2/readonly-ref.md#metadata-representation-of-in-parameters
    Private Shared Function ActuallyReceiveDummy(<System.Runtime.CompilerServices.IsReadOnlyAttribute()> ByRef dummy As Dummy) As Integer
        ' Do whatever you want here but don't modify dummy!
        Return dummy.GetHashCode() 
    End Function
    

    The VB.NET compiler does not seem to know about or account for this attribute in any way.

    Demo #4 here.

  • This whole idea does an end-run around the principle that the framework prevents in parameters from being modified, and so is super hacky. Thus you need to manually take care not to modify your ByRef dummy as Dummy argument in any way.

As an alternative, If you are willing to write a little bit of C# code, then as suggested in this answer by Sjoerd van Kreel to Recording Audio using XtAudio in VB.NET, you could write an adapter extension method in C# that creates a ReceiveDummy delegate from a delegate taking Dummy by value:

public delegate int ReceiveDummyByValue(Dummy dummy);

public static class DummyActions
{
    public static ReceiveDummy AsReceiveByReference(this ReceiveDummyByValue func) => (in Dummy d) => func(d);
}

Then in your main VB.NET code, use it e.g. as follows:

Private Shared Function ActuallyReceiveDummy(ByVal d As Dummy) As Integer
    ' Return whatever is required for dummy
    Return d.GetHashCode()
End Function

Public Shared Function CreateReceiveDummy() As ReceiveDummy
    Return (New ReceiveDummyByValue(AddressOf ActuallyReceiveDummy)).AsReceiveByReference()
End Function
dbc
  • 104,963
  • 20
  • 228
  • 340
  • You might also be able to do something with `System.Linq.Expressions` and `MakeByRefType()`. See e.g. [Supporting "out / ref" parameters in expressions with conversion to "object"](https://stackoverflow.com/q/27474754) or [Is it possible to have an out ParameterExpression?](https://stackoverflow.com/q/12322641). – dbc Jul 22 '23 at 22:53
  • 1
    well that's what I get for asking for function-pointer-casting tricks I guess :) This is exactly what I asked for though, so I'm going to accept the answer. I will leave my original answer to the other post https://stackoverflow.com/questions/76724264/recording-audio-using-xtaudio-in-vb-net "as is", just write a c# wrapper. It'd be good if you could just put a small note on top of your answer, that writing a wrapper in c# is probably the way to go. – Sjoerd van Kreel Jul 22 '23 at 23:11
  • @SjoerdvanKreel - added (though at the bottom, as an alternative). – dbc Jul 22 '23 at 23:38