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:
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.