Reputation: 12051
Wanting to convert a nullable enum to its underlying nullable number. E.g.:
public enum Car
{
Fast = 1,
Slow = 2,
}
public int? CarToInt(Car? car)
{
if (car == null)
return null;
return (int)car.Value;
}
Usage:
Car? car = Car.Slow;
int? num1 = CarToInt(car); // A
int? num2 = (int?)car; // B
My question is, are methods A and B functionally equivalent? They seem to generate same results. Method B looks simpler, so seems preferred, but not sure if I have missed some edge case. Will method B work with all versions of c# which support nullable structs?
Upvotes: 0
Views: 820
Reputation: 652
A nice approach is to implement an extension method:
static class CarExtentions
{
public static int? ToInt(this Car car)
{
return = (int?)car;
}
}
Use it like this:
Car car = Car.Fast;
var intValue = car.ToInt();
Upvotes: 0
Reputation: 1603
The code
Car? car = Car.Slow;
var num2 = (int?)car;
Generated the following IL:
IL_0000: nop
IL_0001: ldloca.s V_0
IL_0003: ldc.i4.2
IL_0004: conv.i8
IL_0005: call instance void valuetype [System.Runtime]System.Nullable`1<valuetype HomeAccounting.Car>::.ctor(!0)
IL_000a: ldloc.0
IL_000b: stloc.2
IL_000c: ldloca.s V_2
IL_000e: call instance bool valuetype [System.Runtime]System.Nullable`1<valuetype HomeAccounting.Car>::get_HasValue()
IL_0013: brtrue.s IL_0020
IL_0015: ldloca.s V_3
IL_0017: initobj valuetype [System.Runtime]System.Nullable`1<int32>
IL_001d: ldloc.3
IL_001e: br.s IL_002d
IL_0020: ldloca.s V_2
IL_0022: call instance !0 valuetype [System.Runtime]System.Nullable`1<valuetype HomeAccounting.Car>::GetValueOrDefault()
IL_0027: conv.i4
IL_0028: newobj instance void valuetype [System.Runtime]System.Nullable`1<int32>::.ctor(!0)
IL_002d: stloc.1
IL_002e: ret
} // end of method Program::Main
As you can see there the compiler itself add call to HasValue(IL_000e) and created branches as in the CarToInt method, so the code looks equivalent.
I could think about the edge case when you cast enum to type different from the enum base type, but since we call conv instruction only if we have value it seems to be equivalent to normal casting - I mean no exception on casting Car? to int? if Car base type is long - while for boxing, e.g. this
double? car = 0.0;
object boxed = car;
var num2 = (short?)boxed;
would throw an exception.
Upvotes: 0
Reputation: 81543
My question is, are methods A and B functionally equivalent
The answer is yes, however they do slightly different things to achieve the same result as you can see here
public int? CarToInt(Car? car)
{
if (car == null)
return null;
return (int)car.Value;
}
// Compiles to
IL_0000: ldarga.s car
IL_0002: call instance bool valuetype [mscorlib]System.Nullable`1<valuetype C/Car>::get_HasValue()
IL_0007: brtrue.s IL_0013
IL_0009: ldloca.s 0
IL_000b: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0011: ldloc.0
IL_0012: ret
IL_0013: ldarga.s car
IL_0015: call instance !0 valuetype [mscorlib]System.Nullable`1<valuetype C/Car>::get_Value()
IL_001a: newobj instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)
IL_001f: ret
// which is equivalent to
if (!car.HasValue)
{
return null;
}
return (int)car.Value;
Apposed to
int? num2 = (int?)car;
// Compiles to
IL_0010: ldloc.0
IL_0011: stloc.2
IL_0012: ldloca.s 2
IL_0014: call instance bool valuetype [mscorlib]System.Nullable`1<valuetype C/Car>::get_HasValue()
IL_0019: brtrue.s IL_0026
// which is equivalent to
int? obj = car2.HasValue ? new int?((int)car2.GetValueOrDefault()) : null;
In summary there is no appreciable difference.
Casting looks more succinct
Upvotes: 2
Reputation: 16826
Yes, they are functionally equivalent, and you don't need to implement a separate function to re-cast the entity unless you know of specific edge cases in which you need to return a custom value.
Recommended reading: Using nullable types (C# Programming Guide)
Upvotes: 2