Switch expression on System.Type in C# 8
Asked Answered
T

3

10

I'm curious is any other way to write something like this with new switch expression in C# 8?

public static object Convert(string str, Type type) =>
    type switch
    {
        _ when type == typeof(string) => str,
        _ when type == typeof(string[]) => str.Split(new[] { ',', ';' }),
        _ => TypeDescriptor.GetConverter(type).ConvertFromString(str)
    };

Because _ when type == typeof(string) looks kinda weird especially when we have type pattern and other very convenient instruments.

Teetotalism answered 21/5, 2020 at 23:4 Comment(5)
No, what you have is the only way I'm afraid.Tunic
Possibly move the typeof to assign to a static variable? Allowing something like _ when type == stringType => str, which may read slightly better?Clotilde
Have you considered using generics rather than returning object?Clotilde
Unfortunately generic method is not suitable in my caseTeetotalism
This does look like a peculiar method though, perhaps it is an XY problem and could be done in a completely different, but better, way.Tunic
W
4

As others have alluded to, you actually need to have an instance of a type available to use the new type-matching features, not the representative System.Type. If you want to match directly on the type, the way you're doing it seems to be the only viable way for the time being.

That being said, I would argue in this case that a standard switch statement might be more readable:

switch (type)
{
    case Type _ when type == typeof(string):
        return str;

    case Type _ when type == typeof(string[]):
        return str.Split(',', ';');

    default:
        return TypeDescriptor.GetConverter(type).ConvertFromString(str);
}

If you really want to keep the switch expression, you could potentially get around this by matching on type names instead, though as a commenter pointed out below, this option is particularly brittle and will not work with certain types (such as DateTime? or Nullable<DateTime>):

public static object Convert(string str, Type type) =>
    type.Name switch
    {
        nameof(string) => str,
        nameof(string[]) => str.Split(new[] { ',', ';' }),
        _ => TypeDescriptor.GetConverter(type).ConvertFromString(str)
    };
Waligore answered 27/5, 2020 at 16:18 Comment(2)
Worth noting - this switching on type.name falls apart when you use nullable fields. e.g. datetime? will be 'nullable`'Plate
Excellent point, I've updated my answer to reflect that caveat.Waligore
R
2

If you would use generics and use the latest c# version you could do something like this:


public partial class DateMultiSelect<TDate> where TDate: struct
{
    protected override void OnParametersSet()
    {
        if(default(TDate) switch 
        {
            DateOnly => true,
            DateTime => true,
            DateTimeOffset => true,
            _ => false
        })
            throw new ArgumentException($"TDate must be a date type, but is {typeof(TDate).Name}");

        base.OnParametersSet();
    }
}

according to your example this would be something along those lines: (can be compiled, but didn't test it at runtime)

public static T? Convert<T>(string? str) =>
        default(T) switch
        {
            string => (T?)(dynamic?)str,
            string[] => (T?)(dynamic?)str?.Split(new[] { ',', ';' }),
            _ => (T?)TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(str)
        };

instead of dynamic also object could be used.

Rostand answered 20/12, 2021 at 17:48 Comment(0)
H
0

In a similar situation I ended up using a generic helper to make intent clearer. I did have <T> type available but not the C#9 default (T) switch { string[] => ...} syntax.

This solution seems to be handling nullables like DateTime? correctly.

static object Convert(string str, Type type)
    => type switch {
        _ when Match<string>(type) => str,
        _ when Match<string[]>(type) => str.Split(new[] { ',', ';' }),
        _ => TypeDescriptor.GetConverter(type).ConvertFromString(str)
    };
static bool Match<T>(Type t) => typeof(T) == t;

For my <T> available case it became something like:

static object Convert<T>(string str)
    => type switch
    {
        _ when Match<string, T>() => str,
        _ when Match<string[], T>() => str.Split(new[] { ',', ';' }),
        _ => TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(str)
    };
static bool Match<TA, TB>() => typeof(TA) == typeof(TB);

edit: One can shorten the syntax a bit with an extension method:

...
public static bool Is<T>(this System.Type type) => type == typeof(T);
...
static object Convert(string str, Type type)
    => type switch {
        _ when type.Is<string>() => str,
        _ when type.Is<string[]>() => str.Split(new[] { ',', ';' }),
        _ => TypeDescriptor.GetConverter(type).ConvertFromString(str)
    };
Heffner answered 29/6, 2022 at 7:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.