Search code examples
vb.netgenericsinterface

From A Method With In The Class Return An Instance Of The Class As An Interface Type That The Class Implements


what I'm trying to archive with the code below is to have the GetInstance generic function take in an interface type that SystemVars implements (say IAuthentication) then create an instance of SystemVars and return it as interface type T.

The problem I an having is that no matter what casting method I try I can't find a way to return the new instance of SystemVars as T. The line in the GetInstance method Return <CastingFunction>(New SystemVars,T) always fails to compile with the error message saying Value of type SystemVars cannot be converted to 'T'.

How do I return the instance of the class as the interface type that was passed into T?

Imports System.Drawing

Public Class SystemVars
    Implements IAuthentication,
               IAuthorization,
               IApplicationStarting

    Private Sub New()
    End Sub


    Public Shared Function GetInstance(Of T)() As T
        Return DirectCast(New SystemVars, T)
    End Function


    Public ReadOnly Property Username As String _
        Implements IAuthentication.Username,
                   IAuthorization.Username
        Get
            Return _userName
        End Get
    End Property


    Public ReadOnly Property Rolls As List(Of String) _
        Implements IAuthorization.Rolls
        Get
            Return _rolls
        End Get
    End Property


    Public ReadOnly Property InstallationId As Guid _
        Implements IAuthentication.InstallationId,
                   IApplicationStarting.InstallationId
        Get
            Return _installationId
        End Get
    End Property


    Public ReadOnly Property MainWindowStartUpPlacement As Rectangle _
        Implements IApplicationStarting.MainWindowStartUpPlacement
        Get
            Return _mainWindowStartUpPlacement
        End Get
    End Property

    '........

    Private Shared _userName As String
    Private Shared _rolls As List(Of String)
    Private Shared _installationId As Guid
    Private Shared _mainWindowStartUpPlacement As Rectangle
End Class

Solution

  • You can make an otherwise illegal cast work by passing through Object.

    Public Shared Function GetInstance(Of T)() As T
        Return DirectCast(CObj(New SystemVars), T)
    End Function
    

    You will get a runtime error if the cast isn't possible; as noted in the comments, this strategy is chucking type safety out the window and basically telling the compiler, "Don't bother me, I know what I'm doing." The runtime will throw an InvalidCastException on failure if you don't test and throw yourself. You can test using Type.IsAssignableFrom if you want to create a more developer-friendly error message; there isn't much context available in the debugger at the point of failure, though it may be pretty obvious if you look up the call stack.

    For just three interfaces, it might be better to do three separate specific functions rather than a generic version, especially considering that the functions are necessarily Shared (and thus can't themselves be part of an interface).

    You might also consider a design that includes a Dependency Injection container. In this kind of design, there would be a configuration step that would associate the interfaces with SystemVars as the implementation, then the client would ask the container for an instance of the interface and receive a SystemVars object.

    The rough way that the three options (the third being to cast the SystemVars object to the requested interface) would look in code is:

    'Casting a received object to a requested interface
    Dim asInterface = DirectCast(SystemVars.GetInstance(), IAuthorization)
    
    'Using a custom casting function on SystemVars
    Dim asInterface = SystemVars.GetInstance(Of IAuthorization)
    
    'Using a DI container
    'Behavior if the interface isn't supported depends on the container
    Dim asInterface = container.GetInstance(Of IAuthorization)
    

    Note that TryCast could be used instead of DirectCast, in which case the result would be Nothing if the interface isn't supported.