19

In C# 7.1 the below is valid code:

object o = new object();
switch (o)
{
    case CustomerRequestBase c:
        //do something
        break;
}

However, I want to use the pattern switch statement in the following scenario:

public T Process<T>(object message, IMessageFormatter messageFormatter) 
    where T : class, IStandardMessageModel, new()
{
    switch (T)
    {
        case CustomerRequestBase c:
            //do something
            break;
    }
}

The IDE gives me the error "'T' is a type, which is not valid in the given context" Is there an elegant way to switch on the type of a generic parameter? I get that in my first example you are switching on the object and the second I'd want to switch on the type T. What would be the best approach to do this?

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
  • You probably want to make use of `typeof(...)`. – user247702 Dec 03 '18 at 16:29
  • 4
    If your method only supports a fixed list of types, it shouldn't be generic in the first place. Just have overloads for the different types your method supports. – Servy Dec 03 '18 at 16:29
  • 7
    Honestly, this looks like a code smell to me. Any time you think you need to `switch` on a type almost certainly means that you should be refactoring the code so that isn't needed. – DavidG Dec 03 '18 at 16:34
  • 4
    @DavidG: Yes, switching on code type is a code smell. But it's one we have all done occasionally over the years - sometimes it's the best (or only) solution to a problem. With the new syntax, the smell is greatly reduced - you get a very clear syntactic rendition of the intent of the program. David Christopher Reynolds: you may want to file an issue with Microsoft. You may have found an edge case they should fix. – Flydog57 Dec 03 '18 at 16:37
  • 5
    @Flydog57 *I've done it before* is a poor reason to suggest doing it again. – DavidG Dec 03 '18 at 16:39
  • 1
    @DavidG: OK, but there are reasons to do this: 1) the types have a common base class (or interface), but you don't control the source for the types (or the cost of the refactor would be too high). You could add an Extension method, but that method would probably include a switch on type. 2) the classes don't have a common inheritance root, and it would make little sense to add one. 3) I'm sure their are others. Sometimes, code smell is unavoidable (particularly when maintaining grotty old code). – Flydog57 Dec 03 '18 at 16:54
  • 2
    @Flydog57 And I bet you 500 rep that none of those apply in this situation... – DavidG Dec 03 '18 at 16:55
  • 2
    @Flydog57 In all those cases simple method overloading is better. What added value does generics bring? You are essentially implementing each overload inside a switch case... – InBetween Dec 03 '18 at 16:56
  • 2
    *Is there an elegant way to switch on the type?* Yes there definitely is: Don't do it. – CodingYoshi Dec 03 '18 at 16:57

5 Answers5

16

Below are two different classes called Foo and Bar. You can use one instance of any of these classes as a parameter to a function named Process. After all, you can perform pattern matching as shown in the example function. There is a function named Test for the usage example..

public class Foo
{
    public string FooMsg { get; set; }
}

public class Bar
{
    public string BarMsg { get; set; }
}

public class Example
{
    public T Process<T>(T procClass) where T : class
    {
        switch (typeof(T))
        {
            case
                var cls when cls == typeof(Foo):
                {
                    var temp = (Foo)((object)procClass);
                    temp.FooMsg = "This is a Foo!";

                    break;
                }

            case
                var cls when cls == typeof(Bar):
                {
                    var temp = (Bar)((object)procClass);
                    temp.BarMsg = "This is a Bar!";

                    break;
                }
        }

        return
            procClass;
    }

    public void Test(string message)
    {
        Process(new Foo() { FooMsg = message });
        Process(new Bar() { BarMsg = message });
    }
}
Abdullah Elen
  • 534
  • 4
  • 13
5

I agree that there are situation when this approach is faster and not so ugly, and also agree that in any case a better solution should be found, but sometimes the trade-off doesn't pay... so here is a solution (C# 9.0)

return typeof(T) switch
{
    Type t when t == typeof(CustomerRequestBase) => /*do something*/ ,
    _ => throw new Exception("Nothing to do")
};
Mosè Bottacini
  • 4,016
  • 2
  • 24
  • 28
0

I'm going to preface by saying that in general I agree with all the commenters that say switching on a generic T is probably not a good idea. In this case I would advice him to stick with identifying the object, casting it, and then passing it to an appropriate handler.

However, I've been writing a custom binary serializer that needs to be fairly efficient and I've discovered one case where I feel the kind of switching (or if statement) he's asking for is justified, so here's how I managed it.


public T ProcessAs<T>(parameters)
{
    if (typeof(T) == typeof(your_type_here)
    {
        your_type_here tmp = process(parameters);
        return Unsafe.As<your_type_here, T>(ref tmp);
    }
    else if (typeof(T) == typeof(your_other_type))
    {
        your_other_type tmp = otherProcess(parameters);
        return Unsafe.As<your_other_type, T>(ref tmp);
    }
    else
    {
        throw new ArgumentException(appropriate_msg);
    }
}

Note that Unsafe.As<T>(T value) can be used if you're dealing with a class instead of a struct.

ashbygeek
  • 759
  • 3
  • 20
0

If we ignore the codesmell discussion as per comments, an easy readable implementation (hack) can look like this:

public T Process<T>(string number)
{   
    switch (typeof(T).FullName)
    {
        case "System.Int32":
            return (dynamic) int.Parse(number);
        case "System.Double":
            return (dynamic) double.Parse(number);
        default:
            throw new ArgumentException($"{typeof(T).FullName} is not supported");
    }
}

Even if you are # with your generic constraints, this is probably bound to cause issues unless you are the sole programmer ;)

noontz
  • 1,782
  • 20
  • 29
0

I've used typeof(T), typeof(T) t when t == typeof(X), typeof(T).Name, and typeof(T).FullName variations (suggested in other answers), but I've never been happy with any of them. They're either too complex, or too slow.

typeof(T) when t == typeof(X) is probably the best, however, it's performance is questionable as the compiler seems to treat the when clauses as a series of if ... else if ... statements. Run the debugger and trace it to see what I mean. String variations have similar concerns - getting type names and comparing strings seems like unnecessary overhead.

What we really want is a solution that uses native switch hash lookup behavior for optimal performance.

So here's another solution that reduces complexity:

  1. Create a dummy variable T
  2. Assign an new value
  3. Use the dummy variable of type T in the switch statement

Result:

public T Process<T>(object message, IMessageFormatter messageFormatter) 
    where T : class, IStandardMessageModel, new()
{
    T dummy = Activator.CreateInstance(typeof(T));
    
    switch (dummy)
    {
        case CustomerRequestBase _:
            //do something
            break;
    }
}

T requires a constructor, which in this case is perfect as the method is qualified with where T: class, new() - being a class and having a default constructor. So we can instantiate the dummy variable, and use that dummy variable to execute standard switch variable case Type functionality.

Warning: T dummy = default won't work as default is usually Null, and we can't use Null in the switch statement. This is why Activator.CreateInstance() was used.

Discard (_) in the case statement is used to discourage use of the dummy variable. Presumably the case statement will have other logic for handling 'message'.

Grant
  • 1,074
  • 1
  • 10
  • 7