Overload Generic Constraints / Cast Generic Type Parameter to (Non-)ValueType

Viewed 69

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.

0 Answers
Related