Reputation: 1451
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?
Upvotes: 21
Views: 17132
Reputation: 15
I was able to get this working by creating a temporary variable of T and then switching case on that. Its a bit of a silly workaround about I think it has the fewest compromises
public void TypeSwitchMethod<T>()
{
T typeHolder = default(T)!;
switch(typeHolder)
{
case Foo:
//DO STUFF
break;
case Bar;
//Do OTHER STUFF
}
}
Upvotes: -3
Reputation: 1164
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:
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'.
Upvotes: 0
Reputation: 761
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.
Upvotes: 0
Reputation: 1997
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 ;)
Upvotes: 0
Reputation: 4216
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")
};
Upvotes: 8
Reputation: 554
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 });
}
}
Upvotes: 17