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.
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.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
- Am I doing any more or less stupid mistake, preventing me from achieving this?
- 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)? - Is there another "third" way, which let's me do this?
- 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.