Call a function based on its type?
-
I'm in C# being bored and refactoring things. I have found an if-ladder that follows this pattern:
//message is a base class, every type below inherits that class if (message is scDoFirstThing) { var firstThingMessage= message as scDoFirstThing; HandleFirstThingAsync(firstThingMessage); } else if (message is scDoSecondThing) { var secondThingMessage= message as scDoSecondThing; HandleSecondThingAsync(secondThingMessage); } else if ( message is scDoThirdThing) ///blah blah {}
I have to wonder if this can't be collapsed to a dictionary of Type,Action? Then the code just finds the entry and if it's found, executes it with that object as a parameter?
Am I off base here?
-
If the type changes from one
Action
to the next, then you can't stick them all in the same Dictionary; that would violate type safety.
-
@Tsaukpaetra you could probably make that work, but I would look at C# pattern matching first. (sorry, on mobile else would have looked up an example) The if(a is b) {var c = a as b;} is an antipattern any way you look at it.
-
Is there any reason why
HandleFristThingAsync
etc. is not part of the respective message class?Anyway, a switch might be prettier:
switch (message) { case scDoFristThing sc1: HandleFristThingAsync(sc1); break; case scDoSecondThing sc2: HandleSecondThingAsync(sc2); break; default: // blah blah }
(even if your linter complains)
-
@Mason_Wheeler said in Call a function based on its type?:
If the type changes from one
Action
to the next, then you can't stick them all in the same Dictionary; that would violate type safety.You can just use
dynamic
to ignore silly things like "type safety."
Filed under: :malcolm:
-
@Applied-Mediocrity said in Call a function based on its type?:
Is there any reason why HandleFristThingAsync etc. is not part of the respective message class?
Because messages shouldn't handle themselves?
Filed under:
-
@Mason_Wheeler said in Call a function based on its type?:
If the type changes from one
Action
to the next, then you can't stick them all in the same Dictionary; that would violate type safety.Yeah I suppose that's the gotcha.
I could change all the target functions' signature to bebase class
type and do the re-casting there?
-
Unless Iâm missing something, in general, to âcall a function based on its typeâ you use dynamic dispatch. Depends on the language whatâs the best way to do that, so Iâll let the C# experts chime in, but commonly instead of an if chain on the type you use a virtual method on the base class.
This might be undesirable if what youâre doing there is something that really doesnât belong in the baseâs interface. In that case, you could use the visitor pattern to just add one genericvisit
method that can then be used for any kind of dynamic dispatch the client side wants to do.
-
@Applied-Mediocrity said in Call a function based on its type?:
Is there any reason why
HandleFristThingAsync
etc. is not part of the respective message class?Apparently the class that's reacting to these messages must do needful things.
-
I feel like 95% of the time, inheritance causes more problems than it solves.
-
-
@Tsaukpaetra said in Call a function based on its type?:
@Applied-Mediocrity said in Call a function based on its type?:
Is there any reason why
HandleFristThingAsync
etc. is not part of the respective message class?Apparently the class that's reacting to these messages must do needful things.
Oh! And some of these are
async void
and not awaited at all!Also also! Every one of these functions checks if its parameter was not null, so why not just accept the
base class
and do the casting there regardless? Dafuq?
-
I seem to recall pulling some shenanigans with the Curiously Recurring Template Pattern, but I also seem to recall it being painful and never wanting to do it again.
I'd go with @Applied-Mediocrity's approach, TBH.
-
@Applied-Mediocrity said in Call a function based on its type?:
(even if your linter complains)
I don't know how to invoke that, or this solution has no such thing.
I think it's basically the same thing as the if-ladder, just less wordy. Meh, I'll take it.
-
This SO guy says you can use the
dynamic
keyword for this?
Or maybe not, Iâve only skimmed a few sentences.
-
@topspin said in Call a function based on its type?:
This SO guy says you can use the dynamic keyword for this?
So did I, but I was 100% trolleybusing.
-
@error said in Call a function based on its type?:
@topspin said in Call a function based on its type?:
This SO guy says you can use the dynamic keyword for this?
So did I, but I was 100% trolleybusing.
Yeah, I figured it was just a way to avoid manually Reflection-ing yourself.
-
@error said in Call a function based on its type?:
@topspin said in Call a function based on its type?:
This SO guy says you can use the dynamic keyword for this?
So did I, but I was 100% trolleybusing.
Iâm not sure what that means (because I donât know what that code actually does).
I just assumed it means you can provide overloads of the function with different signatures (one for each derived class youâre trying to handle) and it does the dynamic dispatch for you. But it sounds like you think itâs a really WTFy way to do things?
-
@topspin said in Call a function based on its type?:
But it sounds like you think itâs a really WTFy way to do things?
TBH I think the language feature is WTFy. It causes things to be late-bound instead of early-bound. It's roughly the same as casting to/from
void *
in C, and just as safe.
-
- Switch tree (nested-ifs in disguise)
- HandleAnyThingAsync((dynamic)message)
Looking up by string, generating runtime delegates, allocating
params
arrays, yadayada@Applied-Mediocrity said in Help Bites:
But
dynamic
is evil.
-
-
(Although I guess providing a link wouldâve been helpful)
-
(message as scDoFirstThing) ?. HandleFirstThingAsync(message as scDoFirstThing); (message as scDoSecondThing) ?. HandleSecondThingAsync(message as scDoSecondThing); ///blah blah
I don't how I feel about this. It's basically a terser version of the OP.
-
How about adding MessageType property to the base class and switching/dictionary calling based on this?
-
You can fool Java with its rubbish generics into this with a Map<Class<?>, Function<? extends MessageBaseType, Action<?> action>> - I've done exactly this in production code. Asking this kind of question is usually a code smell, but to be fair handling arbitrary messages dispatched from one subsystem to another is a reasonable use case.
C# generics are a bit more type safe though and that means you can't con it as easily. You can probably still use a Dictionary<Type, Action<MessageBaseType>>, but the called methods will still have to cast the parameter.
Or yeah you can use dynamic or do some reflective dispatching (I did that in a budget ORM back in the pre-dynamic days) but that's almost certainly worse.
-
This works fine:
interface IMessage {} class scDoFirstThing : IMessage {} class scDoSecondThing: IMessage {} class scDoThirdThing : IMessage {} static void Main() { IMessage frist = new scDoFirstThing() ; DoTheAppropriateThing(frist); IMessage second = new scDoSecondThing(); DoTheAppropriateThing(second); IMessage third = new scDoSecondThing(); DoTheAppropriateThing(third); } static Task DoTheFirstThing (scDoFirstThing message) { Console.WriteLine("Doing the frist thing") ; return Task.CompletedTask; } static Task DoTheSecondThing(scDoSecondThing message) { Console.WriteLine("Doing the second thing"); return Task.CompletedTask; } static Task DoTheThirdThing (scDoThirdThing message) { Console.WriteLine("Doing the third thing") ; return Task.CompletedTask; } static Task DoTheAppropriateThing(IMessage message) { return DoTheAppropriateThing((dynamic)message); } static Task DoTheAppropriateThing(scDoFirstThing message) { return DoTheFirstThing (message); } static Task DoTheAppropriateThing(scDoSecondThing message) { return DoTheSecondThing(message); } static Task DoTheAppropriateThing(scDoThirdThing message) { return DoTheThirdThing (message); }
-
It's interesting that some of the suggested alternatives have significantly more code and complexity than the original code.
-
@bobjanova said in Call a function based on its type?:
Or yeah you can use
dynamic
or do some reflective dispatching (I did that in a budget ORM back in the pre-dynamic
days) but that's almost certainly worse.It's a lot simpler in many ways (provided you have a catch-all handler for
object
or at least the supertype of all types you expect to handle) but the implementation is going to be complicated under the hood whatever you do. You can't really avoid that. (The other way is a CLOS-like multimethod, but that's really just having code to generate theif
tree for you; you've not gained all that much except for some minor syntactic assistance.)The whole area is messy if you want exact types!
-
@Tsaukpaetra are all the handlers a
Action<TMessage>
? Well I presumeFunc<Task,TMessage>
or whatever, but...Why not just rename them all to
HandleMessageThingAsync
, and have different overloads (i.e.scDoFirstThing
,scDoSecondThing
, etc. Then, you can just do some very basic reflection to dispatch. Here's something I did not so long ago...// find a CreateOnboardingControls overload that can accept this page type var method = this.GetType().GetMethod( nameof(CreateOnboardingControls), BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { this.page.GetType() }, null); if (method != null) return new SimplePageControl(method.Invoke(this, new[] { this.page }));
The methods look like this
private IEnumerable<SimplePageControl> CreateOnboardingControls(ListScriptsPage _) private IEnumerable<SimplePageControl> CreateOnboardingControls(ListJobsPage _) private IEnumerable<SimplePageControl> CreateOnboardingControls(ListCompletedJobsPage _)
-
@apapadimoulis yeah it's basically the same as the solutions using
dynamic
but with direct checking the function exists. And it still requires the functions to be named the same and just overloading.It seems the answer really is that there's no clean way to do this as specified...
-
@MrL said in Call a function based on its type?:
How about adding MessageType property to the base class and switching/dictionary calling based on this?
That's how I do it in JS, since it's mostly duck-typed.
-
@Zecc said in Call a function based on its type?:
This works fine:
Except when you add a new message and forget to update code, then instead of a compile/runtime error you get infinite recursion.
-
@Zecc said in Call a function based on its type?:
static Task DoTheAppropriateThing(IMessage message) { return DoTheAppropriateThing((dynamic)message); }
That's actually significantly less ugly than I expected - though, presumably, if you have an IMessage subclass that you haven't written an overload for, it won't resolve and you'll get an exception, so you'd probably want a fallback IMessage overload to generate a sensible error. (Actually in this case I think you'll get a stack overflow because it will resolve to the dispatcher function, but you'd name them differently in the real world.)
In terms of performance this is presumably doing something like the reflective solution (like what Alex posted) under the covers.
-
@GÄ ska said in Call a function based on its type?:
@Zecc said in Call a function based on its type?:
This works fine:
Except when you add a new message and forget to update code, then instead of a compile/runtime error you get infinite recursion.
Just add an additional parameter to the base case or rename it to avoid the stack overflow.
It'll still crash but... it should, in that case.
-
@Carnage said in Call a function based on its type?:
It's interesting that some of the suggested alternatives have significantly more code and complexity than the original code.
That's because people endlessly overthink these things, and end up building monoliths because the simpler solutions have "a problem".
-
@Tsaukpaetra said in Call a function based on its type?:
@apapadimoulis yeah it's basically the same as the solutions using
dynamic
but with direct checking the function exists. And it still requires the functions to be named the same and just overloading.It seems the answer really is that there's no clean way to do this as specified...
Can we take a step up and see what's going on there?
-
@GÄ ska said in Call a function based on its type?:
@Zecc said in Call a function based on its type?:
This works fine:
Except when you add a new message and forget to update code, then instead of a compile/runtime error you get infinite recursion.
Fair enough. Like @dkf said, there should be an overload taking
object
, which will either loudly complain... or not.
-
@xaade said in Call a function based on its type?:
@Tsaukpaetra said in Call a function based on its type?:
@apapadimoulis yeah it's basically the same as the solutions using
dynamic
but with direct checking the function exists. And it still requires the functions to be named the same and just overloading.It seems the answer really is that there's no clean way to do this as specified...
Can we take a step up and see what's going on there?
I don't understand the question.
A UI class is subscribed to a message pump that gives out base-class messages that the receiver is expected to inspect for a subclass it cares about, and thence does needful things accordingly. There's really not much more to it outside the psuedo-code in OP.
-
@bobjanova said in Call a function based on its type?:
you'd name them differently in the real world
-
@bobjanova said in Call a function based on its type?:
Actually in this case I think you'll get a stack overflow because it will resolve to the dispatcher function
I'd expect not, since tail recursion optimisation applies.
-
If this if-ladder contains only a handful of cases then I wouldn't consider refactoring it (well, not unless that same or similar if-ladder is present all over the place, but then you might still use the visitor pattern and put the different logic in the visitor).
At my day job we actually have hundreds of different messages, so updating such an if-ladder would get unwieldy.
What we do instead is to define an interface
IHandler<TMessage>
which only defines aHandle(TMessage message)
method, then register each implementation in some kind of dictionary (we actually use a dependency injection container) which the message pump can query. When it receives a message it will then look at the type of message, check if there's a handler which knows about the type of message and then tries to instantiate such a handler to call itsHandle(message)
method.It's just a way of setting up late-binding or dynamic dispatch in C#, but since the registration step can be automated using reflection we can add messages and handlers without ever having to create conflicts in some other bit of code. And since we use a dependency injection container, the calling code doesn't need to manage all the resources the handler might need so all the usual glue code is hidden.
-
And if you wanted to use someone else's
IHandler<TMessage>
implementation instead of inventing it wholesale, MediatR has you covered.
-
@Carnage said in Call a function based on its type?:
It's interesting that some of the suggested alternatives have significantly more code and complexity than the original code.
You're just jealous because you're not as creative as we are.
static void Main() { IMessage frist = new scDoFirstThing(); TypeMatcher.Of(frist) .TryDoing<scDoFirstThing>(DoTheFirstThing) .TryDoing<scDoSecondThing>(DoTheSecondThing) .TryDoing<scDoThirdThing>(DoTheThirdThing); } sealed class TypeMatcher { readonly IMessage[] Messages; public static TypeMatcher Of(params IMessage[] messages) => new TypeMatcher(messages); public TypeMatcher(params IMessage[] messages) { Messages = messages; } public TypeMatcher TryDoing<T>(Action<T> act) where T: IMessage { foreach(var msg in Messages.OfType<T>()) act(msg); return this; } } static void DoTheFirstThing (scDoFirstThing message) { Console.WriteLine("Doing the frist thing") ; } static void DoTheSecondThing(scDoSecondThing message) { Console.WriteLine("Doing the second thing"); } static void DoTheThirdThing (scDoThirdThing message) { Console.WriteLine("Doing the third thing") ; } interface IMessage {} class scDoFirstThing : IMessage {} class scDoSecondThing: IMessage {} class scDoThirdThing : IMessage {}
-
@Tsaukpaetra said in Call a function based on its type?:
@apapadimoulis yeah it's basically the same as the solutions using dynamic but with direct checking the function exists. And it still requires the functions to be named the same and just overloading.
Um, you just aren't using reflection hard enough. Just add an
s
toGetMethod
, and build your own pattern matching based onHandle*ThingAsync
and parameters. You can even cache this lookup for FAST, or be a true champion andEmit
a method factory.It seems the answer really is that there's no clean way to do this as specified...
Clean... clean? Few things are as pristine and sacrosanct as
System.Reflection
. GTFO with your hereticaldynamic
keyword.
-
@Carnage said in Call a function based on its type?:
It's interesting that some of the suggested alternatives have significantly more code and complexity than the original code.
And what do you propose.... writing actual, business logic??
-
@JBert said in Call a function based on its type?:
It's just a way of setting up late-binding or dynamic dispatch in C#, but since the registration step can be automated using reflection we can add messages and handlers without ever having to create conflicts in some other bit of code. And since we use a dependency injection container, the calling code doesn't need to manage all the resources the handler might need so all the usual glue code is hidden.
One of my new favorite things is code generation with Roslyn Analyzers. You can avoid all the SLOW of dic lookups and dependency injection with compile-time lookups. You will certainly end up with some W1UN analyzer code, but just think of all the CPU Cycles that will be saved in the future.
-
@apapadimoulis said in Call a function based on its type?:
@Carnage said in Call a function based on its type?:
It's interesting that some of the suggested alternatives have significantly more code and complexity than the original code.
And what do you propose.... writing actual, business logic??
I'm busy easter-egging myself a game server into the backend and trying to make a 3D engine for the frontend here. No need to work on business rules.
-
@apapadimoulis said in Call a function based on its type?:
You will certainly end up with some W1UN analyzer code, but just think of all the CPU Cycles that will be saved in the future.
If you're calling this code in your render loop, you can expect to see an improvement of at least 4 FPS!
-
@topspin said in Call a function based on its type?:
@error said in Call a function based on its type?:
@topspin said in Call a function based on its type?:
This SO guy says you can use the dynamic keyword for this?
So did I, but I was 100% trolleybusing.
Iâm not sure what that means (because I donât know what that code actually does).
I just assumed it means you can provide overloads of the function with different signatures (one for each derived class youâre trying to handle) and it does the dynamic dispatch for you. But it sounds like you think itâs a really WTFy way to do things?
I haven't looked at the SO page, and am also responding to a couple days old post so likely 'd, but in my C# experience I've only ever seen
dynamic
used for deserializing JSON objects. You would use Json.Parse (or whatever the syntax is) and assign the result to a dynamic, then write the rest of the code like normal, without any formal type safety other than pinky-promising that whatever .property you write will be there by the time the object is parsed.
-
@hungrier said in Call a function based on its type?:
@topspin said in Call a function based on its type?:
@error said in Call a function based on its type?:
@topspin said in Call a function based on its type?:
This SO guy says you can use the dynamic keyword for this?
So did I, but I was 100% trolleybusing.
Iâm not sure what that means (because I donât know what that code actually does).
I just assumed it means you can provide overloads of the function with different signatures (one for each derived class youâre trying to handle) and it does the dynamic dispatch for you. But it sounds like you think itâs a really WTFy way to do things?
I haven't looked at the SO page, and am also responding to a couple days old post so likely 'd, but in my C# experience I've only ever seen
dynamic
used for deserializing JSON objects. You would use Json.Parse (or whatever the syntax is) and assign the result to a dynamic, then write the rest of the code like normal, without any formal type safety other than pinky-promising that whatever .property you write will be there by the time the object is parsed.This is about the only reason I use it. When working with a web service response, you don't "really" have type-safety anyway because it could send you anything. Having to write
.GetProperty( foo ).GetProperty( bar )
or define a DTO is a lot of effort for little benefit.