3

I want to create an overloaded function with a generic type parameter, which acts slightly different if the generic type is a value type vs a reference type. Imagine "overloading generic type constraints". In case a ValueType is passed, I'd like to use T? instead of T in some places. I could imagine expressing what I want in some different ways in code - unfortunately everything I tried isn't correct code.

  1. Overloading, as with normal parameters
    Have a
    Async Function ReturnAsync(Of T As Class)(...) As Task(Of T) and a
    Async Function ReturnAsync(Of T As Structure)(...) As Task(Of T). This does not work, as the compiler tells I have two definitions with the same signature.

  2. Manually switch and take a different branch
    What I tried, including more complete example of what I want to achieve:

    Public Overrides Function ReturnAsync(Of T)(Optional column As String = "Id") As Task(Of T)
        If GetType(T).IsValueType Then
            Return ReturnAsyncStruct(Of T)(column) ' compiler error
        Else
            Return ReturnAsyncClass(Of T)(column) ' compiler error
        End If
    End Function
    
    Private Async Function ReturnAsyncClass(Of T As Class)(Optional column As String = "Id") As Task(Of T)
        Dim ret As T = Await UpdateOrSelectAsync(Of T)(column)
        If ret Is Nothing Then ret = Await InsertAsync(Of T)(column)
        Return ret
    End Function
    
    Private Async Function ReturnAsyncStruct(Of T As Structure)(Optional column As String = "Id") As Task(Of T)
        Dim ret As T? = Await UpdateOrSelectAsync(Of T?)(column)
        If ret Is Nothing Then ret = Await InsertAsync(Of T)(column)
        Return ret
    End Function
    

    This doesn't work as the calls in line 3 and 5 lead to compiler errors. As T has no constraints applied to it, it can't be used as type parameter with constraints, which makes sense.

Context

All in all I want to provide an API which behaves exactly same for all kinds of types from the callers perspective, but it needs to act slightly different internally given either a value type or a reference type. See the longer above example.

It is an excerpt from an Upsert API I'm writing. It should be compatible i.e. with guid (= string) ids as well as integer ids. Now, when searching for an existing entity, the result could be NULL or a string/an integer. If the entity does not exist, I create it. Hence the internal search-existing-part needs to return "T?" (String for String / Integer? for Integer) while for the public-facing API its fine to always return T.

Questions

  1. Am I doing any more or less stupid mistake, preventing me from achieving this?
  2. Is it possible to "cast" T to be a Class/Structure, failing if casted incorrectly? I believe casting is the wrong kind of thinking about generic types (one does not cast types but instances)?
  3. Is there another "third" way, which let's me do this?
  4. Is achieving this simply impossible?

Environment

I am using VB.Net on full .Net Framework 4.5.2 with Visual Studio 2017. If there is a solution which just works with C#/a newer VS/.Net Framework this would be a pity but I would be interested too.

Georg Jung
  • 949
  • 10
  • 27
  • It might be possible to do something horribly ugly at runtime using reflection, where you would have an actual type associated with `T` and could construct a `Structure`-specific version of it. As a general rule, performance of reflection code is poor, so you wouldn't want to do this if you care about how fast it is. – Craig Oct 22 '18 at 17:47
  • e.g. You could use `GetType(ReturnAsyncStruct(Of ))` to get the incomplete type, then call `MakeGenericType` on it using the current `T`. Note that `MakeGenericType` will throw an `ArgumentException` if the type does not meet constraints; the time I did something like this, I didn't find any way to do a non-throwing test. – Craig Oct 22 '18 at 17:50
  • Thanks for your comments. While I imagined something like this would be possible, I would prefer a solution without reflection. I just can't see why something like what I want should *not* work. – Georg Jung Oct 23 '18 at 07:03
  • 1
    To channel my inner Eric Lippert (and assuming that there isn't another way to do this other than the ugly reflection-based way), it would not work because it was never implemented. I doubt there was an explicit decision that it should be unsupported, but it's also not necessarily a common use case that merits the effort to specify and implement support. – Craig Oct 23 '18 at 13:21
  • Also see: https://stackoverflow.com/questions/7764911/generic-contraints-on-method-overloads – Craig Oct 23 '18 at 13:21

0 Answers0