How to to dynamically provide T type of a generic method
I am having an interface declaration which has many-many implementations. The usecase/logic is similar, but the return type differs.
(tldr: the services parse and validate data extracted from different systems. They each return the parsed structure relevant to the given system and the detected errors).
I want to achieve something like this, however I cannot pass the Type retrieved from the specific implementation in the method call.
The services are provided through factory pattern.
Is it even possible what I want to achieve?
What better solutions/patterns are there to meet my requirements?
59 Replies
You can't
Not without reflections
the alternative I was thinking on was to create some sort of
if
structure and cast directly to the specific interface and remove these methods from the interface declarations
how would I do that with reflections?
like defining it as object and do type analysis in the implementation?Your options there are reflection, a
type
switch, dynamic
, and the visitor patternSomething like
Word of warning: reflections are slow
You'll want to cache all that if you choose to use it
generic.Invoke(this, null);
--> what this
would refer here to?Ah, that's the instance you want to call the method on
So an instance of
MyImplementation
thanks, I will give it a look
this might be a naive question, but you can't just make the interface generic, can you?
thanks, I would want to avoid using dynamics 🙂
Visitor pattern might work as well. Havn't used it yet, but will check this also.
I am not quite sure though if I get what your concept of type switch was?
and so on
What's your actual use-case here?
I fail to see how it would differ vs current solution.
How will I be able to pass the type as a generic?
I am already receiving a system specific implementation that does the logic. But on the caller side, I would want to avoid create a switch case that will handle each implementation and provide a specific type. Thus the
GetMyType
methods
or I miss somethingNope, that's about it. I'm just listing out the 4 options for dealing with this general class of problem
maybe i'm missing something as well, but why can the interface not be generic?
An actual concrete use-case would be helpful. In your toy example,
MyImplementation
already knows the concrete type of MyType
, so it doesn't need that to be passed into MyTypeSpecificMethod
Web API
I have
IMyInterface
, with many implementations.
I am using factory pattern to get the specific implementation.
The user selects a system type and uploads a file for that specific system type.
There are two methods to be called.
1) parse the Stream
into the IList<SystemDto>
(system specific structure)
2) validate the IList<SystemDto>
if they are valid and return the specific errors, if any.SystemDto
is implementation-specific? I'm not sure what you mean by "system specific"yes
So, where does this generic type parameter come in?
I think this is only possible if the interface gets generic, as @ero suggested
Having
IMyInterface<T>
, and a factory which can return different T
s depending on some parameter, doesn't work: the factory needs to return a single type
Can you sketch out some of the actual types and methods involved, so we can talk specifics?I will try, need some time for that
well, this is solvable, but it's not pretty...
I'm assuming it's something like:
... but I don't see why any of those methods need to be generic
That doesn't help? In my example,
CreateHandler
has to return a non-generic II
, which means that nothing uses II<T>
i know :) was just commenting on "the factory needs to return a single type", which would be
II
in my exampleThe point is more, since the code which uses
II
knows nothing of T
, then II<T>
is never used by anyonewell it would be by the impls
Nothing can call
T M()
, since nothing knows what T
is to store the return typeWhy is
AzureDataLoadingService.ParseStreamIntoSystemDto
generic? Can it really handle any sort of T
?
What does the code which calls IDataLoadingInterface.ParseStreamIntoSystemDto
look like? That must be a generic method, because it needs to create a variable of type Result<IList<T>>
?in the generic methods I do a typecheck
I rather suspect, that the problem is that the code which uses
IDataLoadingInterface
needs to be non-generic. I can't see any way that it can be generic. Therefore you need to have IDataLoadingInterface.ParseStreamIntoSystemDto
be non-generic: it needs to return a Result<IList<ISomeBaseDtoType>>
, rather than an implementation-specific DTO type.
That doesn't lose you any type-safety: you've still got a runtime check in AzureDataLoadingService.ParseStreamIntoSystemDto
, expect now you're testing against the runtime type of ISomeBaseDtoType
, and not a generic type parameter
And now that you've removed generics, you don't need all that new T()
stuff: you can just create a new AzureDataDto()
directly
Your real problem is that the code which calls IDataLoadingInterface.ParseStreamIntoSystemDto
can't create a variable to hold the value returned from it. Because in your design, the type of the return value is generic, but the generic isn't known by that code
The only solution is to make the return type non-generic. Reflection doesn't help: that just stores the return value in an object
, which you could just do without reflection by making the method non-generic and making it return object
😛Indeed, however given I cannot enforce some common structure accross each system, I couldn't abstract it a satisfying level.
If I understand you correctly this
ISomeBaseDtoType
interface would be only to *trick *the generic part, so the IDataLoadingInterface
method's doesn't need to be generic?
Thus the interface would look like pretty blank.
interface ISomeBaseDtoType { }
Though I assume this wouldn't make a big difference as I would need to cast it to a specific type anyway, where I again cannot really use the Type retrieved from the methods.If I understand you correctly this ISomeBaseDtoType interface would be only to trickthe generic part, so the IDataLoadingInterface method's doesn't need to be generic?Just a common concrete base type of all of the DTOs, which the calling code can know at compile-time. Make it a marker interface, or just
object
, if you want
Though I assume this wouldn't make a big difference as I would need to cast it to a specific type anyway, where I again cannot really use the Type retrieved from the methods.In the calling (non-generic) code? Yeah you can't access any of the properties of the DTO, because the DTO could be anything. You already have that problem, but you're trying to throw generics at it to try and solve it, but that doesn't help here The real OO way around this is to give the
SystemDto
responsibility for validating itself. That way you don't need the shenanigans with having to pass the DTO (which could be any type) to the handler which expects exactly that type. But given that you've called it a DTO, I'm assuming it's crossing some boundary which means that's not feasible?That could work, however this is an entity and a dto in place. I would love to avoid building logic into them
and that still wouldn't solve to return the system specific errors
What do you mean by "return the system specific errors"?
as my issue is indeed that the return type can change, depending on the system
that I cannot really enforce common structure on the entries
You've got a common error type already, no? With
Result<T>
/ ValidationError
I understand it can be misleading in above example, that error does not hold the specific data.
The Error you took is to handle errors during the method call. But a failed validation can still be a successful call.
The systems specific error would be:
In order to ensure that the given entry of the uploaded data is clearly linked with the error (and the corrections used later on), the error is linked to specific parsed entry.
the data is not stored yet anywhere, thus I don't have any Id reference to rely on..
I'm afraid I didn't follow that. What's "Hr"? What returns a
ValidatedHrCsvData
?the
ValidatedHrCsvData
here extends the SystemDto
replace any HR with Azure. by accident I have just taken a different exampleYour code doesn't create or use a
SystemDto
anywhere, either?here in ConvertDtosToEntries()
(method name is to be fixed though)
And
ValidatedHrCsvData
is a member of SystemDto
?HrCsvData
is a SystemDto
. It s the system dto for HR systems uploaded from CSVSo
SystemDto
isn't actually a type?
In that case, isn't this exactly the problem we've been discussing already? If your IDataLoadingInterface.ParseStreamIntoSystemDto
can return literally any type, with no constraints, then of course the calling code can't access that type in any meaningful wayno, its an example name I used here so we ain't limited by the system.
but there are
AwsSystemDto
, AzureSystemDto
, HrCsvData
(HrSystemDto)
Thus I have created a new contract in the Interface Type GetMyType()
so it would return the specific type
but I understand this cannot workYep, because that
Type
is only known at runtime, but to be able to write code which uses a type, you need to know that type at compile-time
I think you've been designing this entirely from the point of view of the data loading services. Switch it around, and figure out what the API should look like from the code which uses those services
If that code needs to access error information, figure out how you can present that information in a way that code which doesn't know anything about Azure vs HR can access it
(and you might need to do some encapsulation, where your error info DTO only presents basic info, but can write out more complex info to CSV/JSON/a logger/etc if asked)ok, thanks!
it was an educative discussion, I appreciate it!
Glad I could help a bit!
But really, while the service / DTO split is nice and all, encapsulation is really the only neat solution in cases like this
E.g. in a UI framework, you might have Button and TextBox which inherit from Control. Control exposes the general control-related stuff, but the only code which knows how to lay out and render a button is inside the Button class: that code can be called from anywhere, because the Layout and Render methods are on Control, and are non-generic, etc. You don't have a separate ButtonRenderer service and ButtonData DTO
i'm just spitballing, so sorry if this derails the discussion, but static abstract won't help here, right?
No
The problem is that the caller does not know T, and cannot know T
who's the caller again?
The code which takes a Stream and a file type, gets the right sort of service to deal with that file type, gives it the stream, and gets back a set of DTOs which are specific to that file type
I.e. the code which uses all of the code snippets pasted above
mhh... i'm hijacking this thread because i'm interested in this now and need my brain to understand it.
so, an interface like
won't work, because we have, say,
AzureCsvDataParser
and AwsCsvDataParser
?
well i mean... i think you could make it work if IDataLoadingInterface
were generic, maybe...?
eh, what do i knowThe problem is, if that interface is generic, how does the caller provide the right T?
i mean i have like a messed up idea, but since i'm not aware of the full context i'm afraid even that would fall flat...
humor me
this kinda falls apart the instant you need to add more formats
maybe adding
IParserFactory
instead of the specific TParser
could help.
At the end, the parser will have to do the same. Parse<T>(Stream stream)
no matter the implementation. This could reducde the number of generics needed.
Same with TConverter
yet this still wouldn't fix the generic return type problem.
In above example you have used TError
, but it is not in the method signature. So it must be a specific type
unless you abstract the errors to a common structure, disregarding the systemTypeit's a mistake. it is in your signature
the one calling
IDataInterface.Validate<T, TError>(values)
won't know compiletime the type of TError
. Since he the specific implementation of IDataInterface is resolved only during runtime
that's what I understood from above discussion
I am thinking on establishing an abstraction that will grant the errors are on a common structure.
like serializing into a json, or stg. similar
that way the return type can be set in the interface without the need of a generic solution
maybe this would reach the desired result with the least effort while not compromising too much on the standards