GOTCHA #22 enum lacks type-safetyenum provides convenience and improved productivity. The possible values get listed in IntelliSense, so it's easy to select the one you want during programming. If your method takes an enum as a parameter, the users of your API will typically select a value from the list presented by IntelliSense. But unfortunately they don't have to, which could lead to code like that shown in Example 3-5. In this program, Method1() receives an enum and accesses the array resource based on that value. First, you pass three valid values of the Size enum to Method1(). Then, you pass an invalid value of 3. The output is shown in Figure 3-3. Example 3-5. Example to study type-safety of enumC# (EnumSafety) using System; namespace EnumTypesafety { class Program { private static int[] resource = new int[] {0, 1, 2}; public enum Size { Small, Medium, Large } public static void Method1(Size theSize) { Console.WriteLine(theSize); Console.WriteLine("Resource: {0}", resource[(int)theSize]); } [STAThread] static void Main(string[] args) { Method1(Size.Small); Method1(Size.Large); Method1((Size) 1); Method1((Size) 3); } } } VB.NET (EnumSafety) Module Program Private resource() As Integer = New Integer() {0, 1, 2} Public Enum Size Small Medium Large End Enum Public Sub Method1(ByVal theSize As Size) Console.WriteLine(theSize) Console.WriteLine("Resource: {0}", _ resource(Convert.ToInt32(theSize))) End Sub Sub Main() Method1(Size.Small) Method1(Size.Large) Method1(CType(1, Size)) Method1(CType(3, Size)) End Sub End Module So what happens if the value sent in for the enum does not match one of the permissible values? At compile time, no error or warning is reported. Users are allowed to send Method1() an invalid value of 3 for the enum. What's going on here? The answer lies in the translation to MSIL. The MSIL generated from the above code is shown in Figure 3-4. Figure 3-3. Output from Example 3-5Figure 3-4. MSIL for the Main method in Example 3-5There is no difference between passing one of the correct values and one of the incorrect ones. Under the hood, there is no type safety or range checking in place. That's why you get a runtime exception instead of a compile-time error when you access the array with the index provided. Too bad the compiler does not catch this or even give you a warning. What can you do about this? Within methods that receive an enum, make sure the given value is valid. You have to do this before using an enum parameter to make your code robust. A modified Method1() that takes care of this checking is shown in Example 3-6, along with a Main() modified to catch the thrown exception. The output is shown in Figure 3-5. Example 3-6. Example of type-safe usage of enumC# (EnumSafety) using System; namespace EnumTypesafety { class Program { private static int[] resource = new int[] {0, 1, 2}; public enum Size { Small, Medium, Large } public static void Method1(Size theSize) { if(System.Enum.IsDefined(typeof(Size), theSize)) { Console.WriteLine(theSize); Console.WriteLine("Resource: {0}", resource[(int)theSize]); } else { throw new ApplicationException( "Invalid input for Size"); } } [STAThread] static void Main(string[] args) { try { Method1(Size.Small); Method1(Size.Large); Method1((Size)(1)); Method1((Size)(3)); } catch(ApplicationException ex) { Console.WriteLine(ex.Message); Console.WriteLine(ex.StackTrace); } } } } VB.NET (EnumSafety) Module Program Private resource() As Integer = New Integer() {0, 1, 2} Public Enum Size Small Medium Large End Enum Public Sub Method1(ByVal theSize As Size) If System.Enum.IsDefined(GetType(Size), theSize) Then Console.WriteLine(theSize) Console.WriteLine("Resource: {0}", _ resource(Convert.ToInt32(theSize))) Else Throw New ApplicationException( _ "Invalid input for Size") End If End Sub Sub Main() Try Method1(Size.Small) Method1(Size.Large) Method1(CType(1, Size)) Method1(CType(3, Size)) Catch ex As ApplicationException Console.WriteLine(ex.Message) Console.WriteLine(ex.StackTrace) End Try End Sub End Module Figure 3-5. Output after the modifications in Example 3-6This code verifies that the enum value you've been passed is valid by calling System.Enum.IsDefined(). If it is not, it throws an exception. This prevents you from accessing the resource array with an invalid index. While using System.Enum.IsDefined() to verify the enum value may appear logical, there are some inherent problems in using it. First, the call to IsDefined() is expensive, as it relies heavily on reflection and metadata. Second, IsDefined() checks if the value is one of the possible values, not necessarily the ones you expect. This will be a problem if a new value is added to the enum during versioning. Refer to http://blogs.msdn.com/brada/archive/2003/11/29/50903.aspx for more details on this. Another problem with enum types relates to serialization. The deserialization of an enum may break if you change a value in the enum after serializing an object. IN A NUTSHELLDo not assume that the value of an enum received as a method parameter is within range. Check to verify it. This will make your code more robust. Program defensively. SEE ALSOGotcha #15, "rethrow isn't consistent," and Gotcha #16, "Default of Option Strict (off) isn't good." |