3

My context is that I'm building a simple factory method for creating instances of derived types of a given base type. The factory method only takes a type parameter, i.e. doesn't have any arguments. This is obviously possible with an if-else if construct:

public Vehicle Create<T>()
{
    if (typeof(T) == typeof(Car))
        return new Car(); // just an example; could be more complex logic.
    else if (typeof(T) == typeof(Truck))
        return new Truck(); // just an example; could be more complex logic.
    else
        throw new ArgumentException(
            $"The type {typeof(T).Name} is not known by this method.");
}

It's well-known by now how to use pattern matching in C# (as of C# 7.0) to branch based on the type of a variable but this doesn't work for a type parameter:

switch (T) { ... } // error: "'T' is a type, which is not valid in the given context"

or...

switch (typeof(T))
{
    case Car c: ... 
    // err: "An expression of type 'Type' cannot be handled by a pattern of type 'Car'"
}

So I'd like to know if it's possible to use switch to achieve the same result?


Research: I'm surprised this hasn't been asked before, but I can't find it. I found this post which has a name and a few answers that come pretty close but it's dealing with (numeric) value types and methods that have an argument of type T -- the generic type parameter. Similarly, this post also uses an argument.

rory.ap
  • 34,009
  • 10
  • 83
  • 174
  • 1
    No - I'm afraid you're stuck with if/else. At least remember that the JIT knows this pattern, and can optimize for it. – canton7 Apr 09 '19 at 14:03
  • Well, I answered my own question, so it *is* possible. – rory.ap Apr 09 '19 at 14:04
  • 1
    Your answer is just abusing a switch to write an if/else - I'm not sure you can really claim that's a viable alternative to if/else! I could write an if/else using exception filters. Does that mean I should? Absolutely not. – canton7 Apr 09 '19 at 14:04
  • 1
    [switch on type](https://stackoverflow.com/questions/298976/is-there-a-better-alternative-than-this-to-switch-on-type) with a [great answer](https://stackoverflow.com/a/299001/1515209) – qujck Apr 09 '19 at 14:10
  • You could also create a `Dictionary>` or something, of course. – canton7 Apr 09 '19 at 14:11
  • @canton7 -- that wouldn't be nearly the same level of efficiency -- overhead -- as a switch or an else-if, so that's a silly suggestion. – rory.ap Apr 09 '19 at 14:12
  • I'm not sure I understand your attitude. You ask for suggestions, then call them "silly" but defend your own suggestion with "some people might prefer doing this". If you're going to shoot down every else's suggestions, but defend your own to the death, why bother asking the question in the first place? – canton7 Apr 09 '19 at 14:14
  • 1
    Also, an if/else is O(n) (ignoring any JIT optimizations), but a dictionary is O(1). For a large number of items, the dictionary will probably beat the pants off an if/else. Use `RuntimeTypeHandle` for bonus speed points. – canton7 Apr 09 '19 at 14:15
  • Okay I misunderstood the intent of your comment. I thought it was solely to point out that you thought my answer was absurd. My mistake (honestly). – rory.ap Apr 09 '19 at 14:15
  • @qujck -- you misunderstood the question. It's not about variable type, it's about type parameters (generics). – rory.ap Apr 09 '19 at 14:17
  • 1
    See [Is there a better alternative than this to 'switch on type'?](https://stackoverflow.com/a/55573781/266143) for many more absurd suggestions, such as `where T : new() /* ... */ switch (new T()) { ... }`. – CodeCaster Apr 09 '19 at 14:18
  • See: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/switch#type-pattern – Maximilian Burszley Apr 09 '19 at 14:23
  • @CodeCaster -- what does the question you think this is a dupe of have to do with generic type parameters? You really think this is an *exact duplicate* of that question? – rory.ap Apr 09 '19 at 16:21

2 Answers2

2

I know your question specifically asked about using the switch statement, but another alternative would be to create a dictionary of factories keyed on the type.

You should note that at this point, you are doing an operation similar to dependency injection. You are asking for a Vehicle of type X and risking a run-time error if the Create method does not have the information needed to Create a Vehicle of type X.

public class Car : Vehicle { }
public class Truck : Vehicle { }

public abstract class Vehicle
{
    private static readonly IReadOnlyDictionary<Type, Func<Vehicle>> vehicleFactories = new Dictionary<Type, Func<Vehicle>>
    {
        { typeof(Car), () => new Car() },
        { typeof(Truck), () => new Truck() }
    };

    public static Vehicle Create<T>() where T : Vehicle, new()
    {
        if (vehicleFactories.TryGetValue(typeof(T), out var factory))
        {
            return factory();
        }
        else
        {
            throw new ArgumentException(
                $"The type {typeof(T).Name} is not known by this method.");
        }
    }
}
Grax32
  • 3,986
  • 1
  • 17
  • 32
0

EDIT: Note that I'm not making any statement as to whether this is good or bad. It's entirely to show that it's possible to do it.

This can be accomplished in C# 7.0 or later using a switch block with pattern matching and the when keyword:

public Vehicle Create<T>() where T : Vehicle
{
    switch (true)
    {
        case bool x when typeof(T) == typeof(Car): return new Car();
        case bool x when typeof(T) == typeof(Truck): return new Truck();
        default: throw new ArgumentException(
            $"The type {typeof(T).Name} is not known by this method.");
    }
}
rory.ap
  • 34,009
  • 10
  • 83
  • 174
  • 6
    That's just horrible. Why would you ever want to use this in your code? – Patrick Hofman Apr 09 '19 at 14:03
  • 1
    @PatrickHofman -- it's just a POC, and that's totally your opinion unless you can back it up with something factual. – rory.ap Apr 09 '19 at 14:04
  • 1
    There's some elegance to it. It's the type of thing that grows on you. – rory.ap Apr 09 '19 at 14:06
  • Sorry, but as canton7 wrote: Your answer is just abusing a switch to write an if/else. That's just it. – Patrick Hofman Apr 09 '19 at 14:06
  • 1
    But it *is* possible. This question isn't about whether it's good or bad. It's entirely about whether it's possible. – rory.ap Apr 09 '19 at 14:08
  • 2
    So is the question actually "What esoteric ways are there to write an if/else without using an explicit if/else"? I can come up with some horrible answers to that... – canton7 Apr 09 '19 at 14:08
  • You can simplify your one a bit with `switch ((object)null) { case null when ...; case null when ...; }` – canton7 Apr 09 '19 at 14:10
  • 1
    @PatrickHofman -- some people might prefer using a switch instead of an if-else. So what? – rory.ap Apr 09 '19 at 14:10
  • @PatrickHofman This is a Q&A by the same person. You don't get to define what they consider production. – Maximilian Burszley Apr 09 '19 at 14:15
  • @TheIncorrigible1 No. I am just judging the usefulness of this answer. – Patrick Hofman Apr 09 '19 at 14:17
  • @PatrickHofman -- the answer simply points out an alternative to "if-else". Whether it's an abuse of C# is simply a matter of opinion. Unless you can point to some other place this was previously mentioned, I think this could be the only place and thus has *some* degree of utility to *someone*, and that is a mere fact. – rory.ap Apr 09 '19 at 14:20
  • 2
    The problem isn't if/then vs. switch. It's runtime type-checking that will produce unpredictable runtime errors. If I write `Create()` and it compiles because `Bicycle : Vehicle`, but then at *runtime* it says, "Oops, not *that* type of `vehicle`", that's bad. In most - not all - cases if we're doing this sort of runtime type checking something has gone wrong. – Scott Hannen Apr 09 '19 at 18:54