Home > Mobile >  What is a better alternative to using generics with types only known at runtime in this case?
What is a better alternative to using generics with types only known at runtime in this case?

Time:02-23

Right now I can only use the Jumper class for objects of type "Something". I tried rewriting it as a generic class but ran into difficulties when actually calling it from my SomeForm class because "T" can be one of several types that I only know at runtime. I read that this means I am basically fighting the whole generics language design.

The sample code is VB but I could also work with C# answers.

Question: can this be redesigned with generics or what is a better alternative to generics in my case?

Public Class Jumper
    Private _enumeratorForwards As IEnumerator(Of Something)

    Public Sub JumpNext(functions As FunctionCombi)
        Forwards(functions.SelectorFunc)
    End Sub

    Private Iterator Function Forwards(selectorFunc As Func(Of Something, Boolean)) As IEnumerable(Of Something)

End Class

Public Class FunctionCombi
    Public Property SelectorFunc As Func(Of Something, Boolean)

    Sub New(_selectorFunc As Func(Of Something, Boolean))
        SelectorFunc = _selectorFunc
    End Sub

End Class

Public Class SomeForm

    'x and y would be different types
    Private _functionCombis As New Dictionary(Of SomeEnum, FunctionCombi) From {
                                                   {SomeEnum.A, New FunctionCombi(Function(x) RunSomeFunction(x)},
                                                   {SomeEnum.B, New FunctionCombi(Function(y) RunSomeOtherFunction(y))}

    Private Sub SomeHandler(sender as object, e as EventArgs)
        For i = 1 To [Enum].GetValues(GetType(SomeEnum)).Length

            'The type I would need here differs and I only know it at runtime

            Dim functionInfo As FunctionCombi = Nothing
            If Not _functionCombis.TryGetValue(i, functionInfo) Then Continue For
            Dim jumper As Jumper = sender.Tag(2)
        Next
    End Sub
End Class

CodePudding user response:

Generic types are most likely not what you need, as those are resolved in compile time. For different types to be resolved at runtime, use an interface.

If one would be in full control of the types involved, that would simply mean: define an interface and let both types implement that interface.

Public Interface IOfficeObject
    ' any members that Shape and TextRange have in common
End Interface

Public Interface Shape
    Inherits IOfficeObject

    ' any members specific for Shape
End Interface

Public Interface TextRange
    Inherits IOfficeObject

    ' any members specific for TextRange
End Interface

Then Jumper would simply talk to that interface, without being concerned about the underlying implementations. Jumper shouldn't be concerned about those underlying classes, otherwise it would defy the whole point of having both classes in the same IEnumerator.

Public Class Jumper
    Private _enumeratorForwards As IEnumerator(Of IOfficeObject)

    ...
End Class

You did not explain what Shape and TextRange (and any other MS interfaces you may be interested in) have in common, neither did you explain what kind of actions your code will be taking on those objects. This is a shame, as it means I'll have to make my point with some hypothetical examples.

Let's assume Shape and TextRange have the following in common:

  • a property Application of type Application, similar to this
  • a method Copy, similar to this

This makes my common interface:

Public Interface IOfficeObject
    ReadOnly Property Application As Application
    Sub Copy()
End Interface

Unfortunately, you are not in control of Shape and TextRange; they are interfaces defined by Microsoft. Apparently, Microsoft did not bother to define a common interface to be inherited by both Shape and TextRange. I don't know why; I'm not familiar with those interfaces or their history. It may have been an oversight, turning into a legacy.

You can use the adapter pattern to resolve that problem.

Public Class ShapeAdapter
    Implements IOfficeObject

    Private ReadOnly _shape As Shape

    Public Sub New(shape As Shape)
        _shape = shape
    End Sub

    Public ReadOnly Property Application As Application
        Get
            Return _shape.Application
        End Get
    End Property

    Public Sub Copy()
        _shape.Copy()
    End Sub

    ' Any other members, forwarding to _shape
End Class

Public Class TextRangeAdapter
    Implements IOfficeObject

    Private ReadOnly _textRange As TextRange

    Public Sub New(textRange As TextRange)
        _textRange = textRange
    End Sub

    Public ReadOnly Property Application As Application
        Get
            Return _textRange.Application
        End Get
    End Property

    Public Sub Copy()
        _textRange.Copy()
    End Sub

    ' Any other members, forwarding to _textRange
End Class

Producing an enumerable of objects involves wrapping each object in its adapter class. Example:

Dim listOfOfficeObjects As New List(Of IOfficeObject)
listOfOfficeObjects.Add(New ShapeAdapter(shape1))
listOfOfficeObjects.Add(New TextRangeAdapter(textRange1))
listOfOfficeObjects.Add(New ShapeAdapter(shape2))
listOfOfficeObjects.Add(New TextRangeAdapter(textRange2))

(If you prefer a factory over explicit instantiation of ShapeAdapter and TextRangeAdapter, that's fine of course.)

Class Jumper will retrieve these adapters. Consumers will always be talking to these adapters, through interface IOfficeObject. In your own code sample, I recognize two consumers, but I'm sure there will be more in the solution you are working on.

Public Function RunSomeFunction(obj As IOfficeObject) As Boolean
    ' pulling whatever is necessary from IOfficeObject to come to a return value
End Function

Public Function RunSomeOtherFunction(obj As IOfficeObject) As Boolean
    ' likewise
End Function

If a consumer needs anything from Shape or TextRange that is not exposed by the interface and/or not implemented by the adapters, then that is something that needs to be fixed in the interface and the adapters. If it cannot be implemented by the adapters, then there is a flaw in your architecture.

  • Related