Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c# 7.0: switch on System.Type

Tags:

c#

People also ask

Huruf c melambangkan apa?

Logo C merupakan sebuah lambang yang merujuk pada Copyright, yang berarti hak cipta.

C dalam Latin berapa?

C adalah huruf ketiga dalam alfabet Latin. Dalam bahasa Indonesia, huruf ini disebut ce (dibaca [tʃe]).

Bahasa C digunakan untuk apa?

Meskipun C dibuat untuk memprogram sistem dan jaringan komputer namun bahasa ini juga sering digunakan dalam mengembangkan software aplikasi. C juga banyak dipakai oleh berbagai jenis platform sistem operasi dan arsitektur komputer, bahkan terdapat beberepa compiler yang sangat populer telah tersedia.

Bahasa C dibuat pertama kali oleh siapa dan tahun berapa?

Bahasa pemrograman C ini dikembangkan antara tahun 1969 – 1972 oleh Dennis Ritchie. Yang kemudian dipakai untuk menulis ulang sistem operasi UNIX. Selain untuk mengembangkan UNIX, bahasa C juga dirilis sebagai bahasa pemrograman umum.


The (already linked) new pattern matching feature allows this.

Ordinarily, you'd switch on a value:

switch (this.value) {
  case int intValue:
    this.value = Math.Max(Math.Min(intValue, Maximum), Minimum);
    break;
  case decimal decimalValue:
    this.value = Math.Max(Math.Min(decimalValue, Maximum), Minimum);
    break;
}

But you can use it to switch on a type, if all you have is a type:

switch (type) {
  case Type intType when intType == typeof(int):
  case Type decimalType when decimalType == typeof(decimal):
    this.value = Math.Max(Math.Min(this.value, Maximum), Minimum);
    break;
}

Note that this is not what the feature is intended for, it becomes less readable than a traditional if...else if...else if...else chain, and the traditional chain is what it compiles to anyway. I do not recommend using pattern matching like this.


The issue raised here by the OP is that you can't use the new C# 7 type-based switch feature when you don't have an actual instance of the switched-upon type available, and you instead have only have its putative System.Type. The accepted answer, summarized as follows, works well for exact type matching (minor improvement shown here, but see my final example below for yet further streamlining)...

Type type = ...
switch (type)
{
    case Type _ when type == typeof(Int32):
    case Type _ when type == typeof(Decimal):
        this.value = Math.Max(Math.Min(this.value, Maximum), Minimum);
        break;
}

The important point to note is that for derived reference type hierarchies, this will not exhibit the same behavior as an if... else chain which uses the is keyword for matching. Consider:

class TBase { }
class TDerived1 : TBase { }
class TDerived2 : TBase { }
class TDerived3 : TDerived2 { }

TBase inst = ...

if (inst is TDerived1)
{
    // Handles case TDerived1
}
else if (inst is TDerived2)
{
    // Handles cases TDerived2 and TDerived3
}
else if (inst is TDerived3)
{
    // NOT EXECUTED                            <---  !
}

Since TDerived3 "is-a" TDerived2, both cases are handled by the earlier condition when using is matching. This highlights the different runtime semantics between 'strict' or 'exact' type equality versus a more nuanced notion of type compatibility. Because the types in the OP's question were ValueType primitives (which can't be derived-from), the difference couldn't matter. But if we adapt the 'exact type matching' of the accepted answer with the example classes shown above, we will get a different result:

Type type = ...

switch (type)
{
    case Type _ when type == typeof(TDerived1):
        // Handles case TDerived1
        break;

    case Type _ when type == typeof(TDerived2):
        // Handles case TDerived2
        break;

    case Type _ when type == typeof(TDerived3):
        // Handles case TDerived3              <---  !
        break;
}

In fact, C# 7 won't even compile a switch statement which corresponds to the if / else sequence shown earlier. (n.b. It seems like the compiler should detect this as a warning, rather than an error, since the harmless result is just a branch of inaccessible code--a condition which the compiler deems a warning elsewhere--and also considering that the compiler doesn't even detect, at all, the seemingly identical situation in the if / else version). Here's that:

enter image description here

In any case, which one of the alternate behaviors is appropriate, or if it even matters, will depend on your application, so my point here is just to draw attention to the distinction. If you determine that you need the more savvy type-compatibility version of the switch approach, here is how to do it:

Type type = ...

switch (type)
{
    case Type _ when typeof(TDerived1).IsAssignableFrom(type):
        // Handles case TDerived1
        break;

    case Type _ when typeof(TDerived2).IsAssignableFrom(type):
        // Handles cases TDerived2 and TDerived3
        break;

    case Type _ when typeof(TDerived3).IsAssignableFrom(type):
        // NOT EXECUTED                       <-- !
        break;
}

Finally, as I mentioned in another answer on this page, you can simplify this usage of the switch statement even further. Since we're only using the when clause functionality, and since we presumably still have the original switched-upon Type instance available in a variable, there's no need to mention that variable in the switch statement, nor repeat its Type (Type, in this case) in each case. Just do the following instead:

Type type = ...

switch (true)
{
    case true when typeof(TDerived1).IsAssignableFrom(type):
        break;

    case true when typeof(TDerived2).IsAssignableFrom(type):
        break;

    case true when typeof(TDerived3).IsAssignableFrom(type):
        break;
}

Notice the switch(true) and case(true). I recommend this simpler technique whenever you are relying only on the when clause (that is, beyond just the situation of switching on System.Type as discussed here).


Starting with Paulustrious's idea of switching on a constant, but striving for the most readability:

  Type type = GetMyType();
  switch (true)
  {
    case bool _ when type == typeof(int):
      break;
    case bool _ when type == typeof(double):
      break;
    case bool _ when type == typeof(string):
      break;
    default:
      break;
  }

What's readable is subjective. I used to do something similar in VB a long time ago so I got used to this form (but in VB the bool _ was not needed so it wasn't there). Unfortunately in c# the bool _ required. I'm using c# 7.0 and I think switching on a constant may not be supported in earlier compilers but I am not sure about that, so try it if you want to. I think it's kindof amusing that the S/O code formatter doesn't know about when yet.

You wouldn't want to do this of course if you need the case variable, like for subclasses.

But for arbitrary boolean expressions it is more suited, for example:

  switch (true)
  {
    case bool _ when extruder.Temperature < 200:
      HeatUpExtruder();
      break;
    case bool _ when bed.Temperature < 60:
      HeatUpBed();
      break;
    case bool _ when bed.Y < 0 || bed.Y > 300:
      HomeYAxis();
      break;
    default:
      StartPrintJob();
      break;
  }

Some will argue this is worse than if..else. The only thing I can say is switch forces one path and it's impossible to break the switch statement itself, but it is possible to leave out an else and break an if..else into multiple statements unintentionally, possibly executing two "branches" accidentally.

Switching on a Type is really just an arbitrary switch because what we are really switching on is a property of the variable. Unless and until we can do case typeof(int) (case on something that is not a constant expression), we are stuck with something akin to this if we don't want to use string constants, which in the case of type names, are not in the framework.


@toddmo suggested the following:

switch (true)
{
    case bool _ when extruder.Temperature < 200:
        HeatUpExtruder();
        break;

    // etc..
    default:
        StartPrintJob();
        break;
}

...but why not go even further in his pursuit of simplicity. The following works just as well without needing the bool type qualification, nor the extraneous _ dummy variable:

switch (true)
{
    case true when extruder.Temperature < 200:
        HeatUpExtruder();
        break;

    // etc.
    default:
        StartPrintJob();
        break;
}