8

EDIT: This is now available in C# 7.0.


I have the following piece of code that checks a given PropertyInfo's type.

PropertyInfo prop;

// init prop, etc...

if (typeof(String).IsAssignableFrom(prop.PropertyType)) {
    // ...
}
else if (typeof(Int32).IsAssignableFrom(prop.PropertyType)) {
    // ...
}
else if (typeof(DateTime).IsAssignableFrom(prop.PropertyType)) {
    // ...
}

Is there a way to use a switch statement in this scenario? This is my current solution:

switch (prop.PropertyType.ToString()) {
    case "System.String":
        // ...
        break;
    case "System.Int32":
        // ...
        break;
    case "System.DateTime":
        // ...
        break;
    default:
        // ...
        break;
}

I don't think this is the best solution, because now I have to give the fully qualified String value of the given type. Any tips?

budi
  • 6,351
  • 10
  • 55
  • 80
  • i think there is no way. Switch operate mainly on string, int and enum (int again bheind the scene). With is assignable from you can check even type that inherits form another, with string this is definetly impossibile. – Skary Dec 17 '15 at 23:09
  • if Types your are working with are only Int32, string and DateTime, your if can use == instead of IsAssignableFrom (they are sealed classes): if (prop.PropertyType == typeof(string) ... But anyway, you cannot use typeof(someClass) as a case label, neither you can use a Type as a switch argument. I would stay with the if – Gian Paolo Dec 17 '15 at 23:10
  • Possible duplicate of [Is there a better alternative than this to 'switch on type'?](http://stackoverflow.com/questions/298976/is-there-a-better-alternative-than-this-to-switch-on-type) – Dmitry Dec 17 '15 at 23:21
  • This feature (called pattern matching) will probably be introduced in [C# 7](https://github.com/dotnet/roslyn/issues/2136). – Olivier Jacot-Descombes Dec 17 '15 at 23:22
  • If you are using C#6 please see this [answer](http://stackoverflow.com/a/299001/2878550). – Dmitry Dec 17 '15 at 23:23

6 Answers6

7

This is now available in C# 7.0.

This solution is for my original question; switch statements work on the value of the PropertyInfo and not its PropertyType:

PropertyInfo prop;

// init prop, etc...

var value = prop.GetValue(null);

switch (value)
{
    case string s:
        // ...
        break;
    case int i:
        // ...
        break;
    case DateTime d:
        // ...
        break;
    default:
        // ...
        break;
}

Slightly more generic answer:

switch(shape)
{
    case Circle c:
        WriteLine($"circle with radius {c.Radius}");
        break;
    case Rectangle s when (s.Length == s.Height):
        WriteLine($"{s.Length} x {s.Height} square");
        break;
    case Rectangle r:
        WriteLine($"{r.Length} x {r.Height} rectangle");
        break;
    default:
        WriteLine("<unknown shape>");
        break;
    case null:
        throw new ArgumentNullException(nameof(shape));
}

Reference: https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/

budi
  • 6,351
  • 10
  • 55
  • 80
  • I got "Non-static method requires a target" when trying to `prop.GetValue(null)`. – rtf Sep 12 '17 at 00:03
  • Hmm, and if I do `prop.GetValue(model)` (with an instantiated class that I'm working with) the pattern matching seems to only work with bool, and otherwise hits the default case. – rtf Sep 12 '17 at 00:17
  • 1
    @TannerFaulkner Can you provide a .NET Fiddle please? – budi Sep 12 '17 at 15:25
  • 1
    Here's where I get the ["Non-static" error](https://dotnetfiddle.net/fkcjBj). Not sure what I'm doing wrong there but null seemed wacky in this case anyway. I think my actual problem is that it [doesn't play nice with null](https://dotnetfiddle.net/J3Igvq). – rtf Sep 12 '17 at 17:05
  • 1
    I'm trying to remember what context I used for the first code example, but I can confirm that it doesn't work... I'll dig a little deeper when I get the chance. For your second fiddle, you're correct. Unfortunately, we cannot differentiate reference types when the given property is not initialized. EX: `default(string) == null` – budi Sep 12 '17 at 17:46
5

I'll answer the question exactly as asked: There is no way.

switch as of C# 6 only supports matching constants of certain types exactly. You are not trying to match constants. You are invoking the IsAssignableFrom method many times.

Note, that IsAssignableFrom is not identical to matching types exactly. Therefore, any solution based on equality comparisons or hash tables can't work.

I think the if ... else if solution that you have is totally fine.

usr
  • 168,620
  • 35
  • 240
  • 369
3

There's no general way, but more often that not, those branches contain very similar code. One pattern that almost always works for me is to use a dictionary;

var myIndex = new Dictionary<Type, string> {
    { typeof(string), "some text" },    
    { typeof(int), "a whole number" },    
    { typeof(decimal), "a fraction" },    
};

string description;
if (myIndex.TryGetValue(prop.PropertyType, out description)) {
    Console.WriteLine("This type is " + description);
} else {
    // 'default'
}
Steve Cooper
  • 20,542
  • 15
  • 71
  • 88
1

Use the ToString method you have, but instead of literal values use case typeof(string).Name (if that's possible) don't have vs in front of me right now.

Steve Harris
  • 5,014
  • 1
  • 10
  • 25
1

First of all IsAssignableFrom is better then just string comparing in case of inherited types. For example typeof(TextReader).IsAssignableFrom(typeof(StreamReader)) will be true, because StreamReader inherited from TextReader, also work's for interfaces.

If you need only direct comparison, I can suggest create Dictionary<Type,Action<PropertyInfo>>, for example:

var typeSelector = new Dictionary<Type, Action<PropertyInfo>>()
{
    {typeof(int), IntAction }
    {typeof(string), StringAction }
    {typeof(DateTime), DateTimeAction }
};

Then you can use it like this:

Action<PropertyInfo> action;
if (typeSelector.TryGetValue(prop.PropertyType, out action))
    action(prop);
else 
    throw new InvalidDataException("Unsupported type");

Sure in this case you will have to create method for each type, or write code during creation of dictionary.

Andrey Tretyak
  • 3,043
  • 2
  • 20
  • 33
0

As shown in Mårten Wikström's answer: "How to use switch-case on a Type?" you can use Type.GetTypeCode as such:

switch (Type.GetTypeCode(type))
{
    case TypeCode.Int32:
       // It's an int
    break;

    case TypeCode.String:
       // It's a string
    break;

    // Other type code cases here...

    default:
        // Fallback to using if-else statements...
        if (type == typeof(MyCoolType))
        {
           // ...
        }
        else if (type == typeof(MyOtherType))
        {
           // ...
    } // etc...
}
Joezer
  • 625
  • 1
  • 9
  • 20