Click here to Skip to main content
15,881,172 members
Articles / Programming Languages / C#
Tip/Trick

Summary of the Behaviour Exhibited by Enum.IsDefined Method (.NET)

Rate me:
Please Sign up or sign in to vote.
4.82/5 (3 votes)
16 Aug 2017CPOL10 min read 24.4K   4   6
What responses to expect from the enum.IsDefined method in .NET given a variety of inputs

Introduction

It was simply that I wanted to protect against erroneous input to a method that took an enum as a parameter, only I wasn't sure how to go about it. So I stumbled upon Enum.IsDefined and then spent quite some time trying to work out how it behaved under different circumstances, i.e., what response was given to different inputs. On reflection, neither using an enum in the particular scenario I had nor using IsDefined are that smart, using the Strategy Pattern[^] instead would probably be a better idea.

So whilst there are better ways to tackle most problems such that you can avoid using enums and switches (and IsDefined) altogether, if you ever do need to use Enum.IsDefined and want some info on its behaviour, then this may help as a reference.

Summary

The rest of the article explains how I came to this conclusion, stop after the table below if all you want is the summary.

Be aware that this table only applies if you create the object and send it to Enum.IsDefined from within the same method or at least you have a way of not needing to pass the object to the method that contains the Enum.IsDefined call.

As soon as you start passing the object which may (or may not) be an enum of the relevant type to other methods (which is essentially the scenario I had and within which I wanted to protect against bad input), you very quickly get into a mess with passing things as generic objects rather than specific enum objects followed by casting exceptions (or you can just go directly to casting exceptions if you prefer). This almost answers the original question in fact.

Summary of the Behaviour exhibited by Enum.IsDefined method
InputExample ValueReturnsException
Member of enumerationTestEnum.BeesTrue-
String representation of member of enumeration"Bees"True-
Integer within range of the enumeration3True-
Integer variable within range of the enumerationint num = 3True-
String representation of integer within range of the enumeration"three"False-
Integer outside the range of the enumeration7False-
Integer variable outside range of the enumerationint num = 7False-
String representation of integer outside range of the enumeration"seven"False-
Member of another enumeration within same range as test enumerationOtherEnum.Bandersnatch-ArgumentException
String representation of member of another enumeration within same range as test enumeration"Bandersnatch"False-
Member of another enumeration outside range of test enumerationOtherEnum.Manxome-ArgumentException
String representation of member of another enumeration outside range of test enumeration"Manxome"False-
Object, not part of an enumerationColor.Blue-InvalidOperationException
Nullint? num = null-ArgumentNullException

Two scenarios, (both within the .NET environment):

First Scenario

In the first one, we do everything in one method or have some way of passing the object under investigation without needing to cast. The table above represents this scenario exactly and there are essentially no constraints in terms of what objects you can throw at IsDefined since its signature is (Type enumType, Object value).

Second Scenario

In the second, we pass an object that has as its type the type of the relevant enum to some method (e.g. after refactoring for the above example). Here, it's somewhat simpler:

  1. An enum is a value type so is not nullable
  2. Any attempt to pass anything other than the relevant enum type to the method that does the test work simply won't compile

So it would appear that in this scenario, the only thing we need to protect against is a change to the enum which is not reflected in the switch statement, and this isn't a problem that IsDefined will help us with.

Errors caused by casting another type to the relevant enum type are outside the scope of what we are trying to protect against and would need to be handled by the calling code.

What would happen if this was a library and an external program tried to access the method with bad data? (I have no idea.)

Contents

  1. Background
  2. Trapping Exceptions
    1. Syntax
    2. Returns
    3. Exceptions
      1. ArgumentNullException
      2. ArgumentException
      3. InvalidOperationException
  3. Code to Discover Behaviour
  4. Code to Handle Bad Input
  5. Points of Interest
  6. History

Background

Example method, the intention was to allow the user of the class that contains this method to specify one of various different ways of converting a color to grey.

C#
ToGrey(ToGreyMethod toGreyMethod)
{
    Switch (toGreyMethod) {
        case ToGreyMethod.Decompose:
            this.Decompose;
            break;
        case ToGreyMethod.Desaturate:
            this.Desaturate;
            break;
        case ToGreyMethod.Brightness:
            this.Brightness;
            break;
        default:
            throw new NotImplementedException("We are dreadfully sorry, 
            but we really don't understand what has caused this error. 
            Please do let us know about our intolerable incompetence.", ex);
            break;
     }
}

Do we need to trap any exceptions that could be caused by any errors emanating from the supplied toGreyMethod?
Probably we do, yes: Exceptions for Enum Method Parameters[^]

How Can We Do That?

Well, before we try and use the supplied method parameter, we can test to see if it is indeed a member of the enumeration and the enum class provides a method to do so, Enum.IsDefined.

And that's when my problems[^] really started. The IsDefined method is defined thus:

Syntax

public static bool IsDefined(
    Type enumType,
    Object value
)

Returns

true if a constant in enumType has a value equal to value; otherwise, false.

Exceptions

ArgumentNullException

enumType or value is null.

ArgumentException

enumType is not an Enum.
-or-
The type of value is an enumeration, but it is not an enumeration of type enumType.
-or-
The type of value is not an underlying type of enumType.

InvalidOperationException

value is not type SByte, Int16, Int32, Int64, Byte, UInt16, UInt32, or UInt64, or String.

To which my first question was: How can that ever return false? It seemed to be that it was either going to return true or throw an exception.

It looked like the exceptions were being used to provide information with a bit more depth than returning a simple false. That doesn't feel particularly good, so either I'm using the method in a way that it wasn't intended to be used or it's not the greatest piece of design ever to escape into production.

Trying to work this out logically on paper and my head wasn't going to work, so time for a scratch project to test what happens when you throw various inputs at the IsDefined method.

Test Code

There are two enumerations, TestEnum with 6 members and OtherEnum with 7 members. TestEnum is the one we will test the objects against for membership.

The static void EnumIsDefined() method sets up an array of objects, each of which we will test in the IsDefined method to see what happens, what errors are thrown, etc. We have populated the array with 7 objects to determine the behaviour of IsDefined in a variety of circumstances.

  1. TestEnum.Bees: This is a member of TestEnum and so should return true with no exceptions
  2. string variable = "Bees": Can the method manage the string representation of enumeration member?
  3. 3 (integer): There are more than three members of the enumeration so this should also return true with no exceptions
  4. integer variable = 3: Is there a difference between the number 3 and a variable with a value of 3 (I hope not!)?
  5. string variable = "three": And what about a string representation of an integer?
  6. 7 (integer): There are only 6 members of the enumeration, I wasn't clear whether this would throw an exception or return false
  7. integer variable = 7: Is there a difference between an out of range number and a variable with the same value?
  8. string variable = "seven": And what about a string representation of an out of range integer?
  9. OtherEnum.Bandersnatch: This is a member of another enumeration but its position in that enumeration (2nd) is within the range of members of TestEnum (6)
  10. string variable = "Bandersnatch": Is there a difference between a member of a different enumeration and its string representation, positional value in range of test enumeration?
  11. OtherEnum.Manxome: This is a member of another enumeration and its position in that enumeration (7th) is outside the range of members of TestEnum (6)
  12. string variable = "Manxome": Is there a difference between a member of a different enumeration and it's string representation, positional value out of range of test enumeration?
  13. Color.Blue: Just a random object I picked to see what happens
  14. Null: Specific case

You can see that we've extracted out a method to provide greater detail concerning any exception that is thrown but, and this will become important later, the array of objects is tested against IsDefined within the same method that we enumerated over the array, i.e., we are not passing the enum from one method to another.

C#
using System;
using System.Drawing;

namespace Cs_Scratch_Console
{
    class Program
    {
        static void Main(string[] args)
        {
            EnumIsDefined();
        }
      
        /// <summary>
        /// Tests how the Enum.IsDefined responds to various diffferent inputs
        /// </summary>
        /// <remarks>http://www.codeproject.com/Questions/642690/Net-Enum-IsDefined-What-am-I-missing
        /// </remarks>
        static void EnumIsDefined()
        {
            Console.WriteLine("Press any key to test the behaviour of Enum.IsDefined...");
            Console.WriteLine();
            Console.ReadKey(true);

            object value;
            int? num = null;
            string printValue;

            object[] values = new object[] { TestEnum.Bees, 3, 7, OtherEnum.Bandersnatch, 
                              OtherEnum.Manxome, Color.FromName("Blue"), num };

            for (int counter = 0; counter <= values.Length - 1; counter++ )
            {
                value = values[counter];
                if (value == null)
                {
                    printValue = "Null";
                }
                else
                {
                    printValue = value.ToString();
                }
                Console.WriteLine("Current array counter is: {0}", counter);
                Console.WriteLine("Current test value is: {0}", printValue);
                if (value != null)
                {
                    Console.WriteLine("It has a type of: {0}", value.GetType());
                }
                else
                {
                    Console.WriteLine("It has a type of: No Type available, object is null");

                }
                Console.WriteLine("Press any key to test whether {0} throws an exception 
                    when we try 'Enum.IsDefined(typeof(TestEnum), {0})'...", printValue);
                Console.ReadKey(true);
                try
                {
                    if (Enum.IsDefined(typeof(TestEnum), value))
                    {
                        Console.WriteLine("{0} is recognised as a defined enumeration 
                                           constant within TestEnum", printValue);
                        Console.ReadKey(true);
                        Console.WriteLine();
                        Console.WriteLine("Press any key for the next test...");
                        Console.WriteLine();
                        Console.ReadKey(true);
                    }
                    else
                    {
                        Console.WriteLine("{0} is NOT recognised as a defined enumeration 
                                           constant within TestEnum", printValue);
                        Console.ReadKey(true);
                        Console.WriteLine();
                        Console.WriteLine("Press any key for the next test...");
                        Console.WriteLine();
                        Console.ReadKey(true);
                    }

                }
                catch (Exception ex)
                {
                    Console.WriteLine("An error was thrown. Press any key to find out more 
                                       about which error...", printValue);
                    Console.ReadKey(true);
                    EnumIsDefinedConsoleWriter(ex);
                    Console.WriteLine();
                    Console.WriteLine("Press any key for the next test...");
                    Console.WriteLine();
                    Console.ReadKey(true);
                }
            }
            Console.WriteLine("Press any key to close the demo...");
            Console.ReadKey(true);
        }

        /// <summary>
        /// Describes which error was thrown by the Enum.IsDefined method
        /// </summary>
        /// <param name="ex">The exception, thrown by Enum.IsDefined, to describe</param>
        static void EnumIsDefinedConsoleWriter(Exception ex)
        {
            Console.WriteLine(ex.GetType().ToString());
            switch (ex.GetType().ToString())
            {
                case "System.ArgumentNullException":
                    Console.WriteLine("The supplied method parameter, which should be a member 
                                      of the enumeration TestEnum, is Null (Nothing in Visual Basic)");
                    break;
                case "System.ArgumentException":
                    Console.WriteLine("The supplied method parameter is a member of an enumeration 
                                      but it is not of the type TestEnum");
                    break;
                case "System.InvalidOperationException":
                    Console.WriteLine("The value parameter provided is not a number of type SByte, 
                    Int16, Int32, Int64, Byte, UInt16, UInt32, or UInt64, or a String representation 
                    of the name of an enumeration member.");
                    break;
          default:
                    Console.WriteLine("We are dreadfully sorry, but we really don't understand 
                                       what has caused this error. Please do let us know about 
                                       our intolerable incompetence.");
                    break;
         }
        }

        /// <summary>
        /// An enumeration for use in testing the Enum.IsDefined method
        /// </summary>
        public enum TestEnum
        {
            Sieve,
            Timballo,
            Owl,
            Bees,
            Pig,
            Stilton
        }

        /// <summary>
        /// An enumeration for use in testing the Enum.IsDefined method
        /// </summary>
        public enum OtherEnum
        {
            JubJub,
            Bandersnatch,
            Vorpal,
            Tumtum,
            Tulgey,
            Beamish,
            Manxome
        }
    }
}

As noted above, we loop through the array with each array member being tested against the enumeration with the Enum.IsDefined method. A positive match gets one message and a negative result another message. We catch and report the exceptions individually, this way we get to know exactly what type of 'error' in the parameter passed to the IsDefined method causes which type of exception.

Summary of the Behaviour exhibited by Enum.IsDefined method
InputExample ValueReturnsException
Member of enumerationTestEnum.BeesTrue-
String representation of member of enumeration"Bees"True-
Integer within range of the enumeration3True-
Integer variable within range of the enumerationint num = 3True-
String representation of integer within range of the enumeration"three"False-
Integer outside the range of the enumeration7False-
Integer variable outside range of the enumerationint num = 7False-
String representation of integer outside range of the enumeration"seven"False-
Member of another enumeration within same range as test enumerationOtherEnum.Bandersnatch-ArgumentException
String representation of member of another enumeration within same range as test enumeration"Bandersnatch"False-
Member of another enumeration outside range of test enumerationOtherEnum.Manxome-ArgumentException
String representation of member of another enumeration outside range of test enumeration"Manxome"False-
Object, not part of an enumerationColor.Blue-InvalidOperationException
Nullint? num = null-ArgumentNullException

With that, we can probably write a sensible method that captures the various possible results and responds appropriately and informatively in each case.

Potential Code

So here goes, we have three cases, positive match, no match and an exception (three types of exception).

Warning! There are better ways to do this type of stuff, it's a demo to work out how Enum.IsDefined functions.

C#
static void ExampleEnumMethod(TestEnum method)
{
    try
    {
        if(Enum.IsDefined(typeof(TestEnum), method))
        {
            switch (method) {
                case TestEnum.Bees:
                    Console.WriteLine("Bees");
                    break;
                case TestEnum.Owl:
                    Console.WriteLine("Owl");
                    break;
                case TestEnum.Pig:
                    Console.WriteLine("Pig");
                    break;
                case TestEnum.Sieve:
                    Console.WriteLine("Sieve");
                    break;
                case TestEnum.Stilton:
                    Console.WriteLine("Stilton");
                    break;
                case TestEnum.Timballo:
                    Console.WriteLine("Timballo");
                    break;
                default:
                    Console.WriteLine("It would appear that the TestEnum enumeration has been changed 
                    or extended but that this method has not been updated to match the new definition, 
                    we are dreadfully sorry about that.");
                    break;
        }
        else
        {
            // throw new ArgumentException("The supplied method parameter is not an enumerable type 
            // but we were able to test it against TestEnum to see if we could make a match, 
            // for a example a string or an integer, unfortunately there was no member of TestEnum 
            // which appeared to match the supplied object", "method");

            Console.WriteLine("The supplied method parameter is not an enumerable type 
            but we were able to test it against TestEnum to see if we could make a match, 
            (for a example it was a string or an integer), unfortunately there was no member 
            of TestEnum which appeared to match the supplied object");
        }
    }
    catch (Exception ex)
    {
        switch (ex.GetType().ToString())
        {
            case "System.ArgumentNullException":
                // throw new ArgumentNullException("The supplied method parameter, 
                // which should be a member of the enumeration TestEnum, 
                // is Null (Nothing in Visual Basic)", ex);
                Console.WriteLine("The supplied method parameter, 
                which should be a member of the enumeration TestEnum, 
                is Null (Nothing in Visual Basic)");
                break;
            case "System.ArgumentException":
                // throw new ArgumentException("The supplied method 
                // parameter is a member of an enumeration but it is not of 
                // the type TestEnum", "method", ex);
                Console.WriteLine("The supplied method parameter is a 
                member of an enumeration but it is not of the type TestEnum");
                break;
            case "System.InvalidOperationException":
                // throw new InvalidOperationException("The value parameter 
                // provided is not a number of type SByte, Int16, Int32, Int64, Byte, 
                // UInt16, UInt32, or UInt64, or a String representation of 
                // the name of an enumeration member.", ex);
                Console.WriteLine("The value parameter provided is not a 
                number of type SByte, Int16, Int32, Int64, Byte, UInt16, UInt32, 
                or UInt64, or a String representation of the name of an enumeration member.");
                break;
            default:
                // throw new ArgumentException("We are dreadfully sorry, 
                // but we really don't understand what has caused this error. 
                // Please do let us know about our intolerable 
                // incompetence.", "method", ex);
                Console.WriteLine("We are dreadfully sorry, 
                but we really don't understand what has caused this error. 
                Please do let us know about our intolerable incompetence.");
                break;
        }
    }
}

Only it's not that simple. You can't compile this when the method that sends the object to test to the ExampleEnumMethod method is an array of object, i.e., since the signature of the method is (TestEnum method) and the array is of objects the compiler complains, not unreasonably. So I changed the calling line from ExampleEnumMethod(value); to ExampleEnumMethod((TestEnum)value);.

Only you can't cast an obect to an enum, the compiler doesn't complain but at run-time, you get invalid cast exceptions. You can't even pass the types that can be explicitly tested against the enum by the EnumIsDefined method, SByte, Int16, Int32, Int64, Byte, UInt16, UInt32, or UInt64, or String, i.e., a string representation of the name of an enumeration member or a value of the underlying type of the enum (or one that can be implicitly cast to it).

I am therefore guessing that as long as you are in the .NET environment, then:

  1. You simply can't compile code that tries to pass anything other than a variable of the relevant enum type to a method with that enum in the method signature
  2. Since an enum is not nullable, we don't need to protect against Null values
  3. You can't pass an array of the relevant enum type (compiler complains again) so we don't need to handle that case
  4. If we wanted to offer the ability to pass integers or strings, we would need to overload, otherwise the user of the method will need to handle this themselves

If of course, we want to offer this as part of some class in a library, then we might want to behave differently, I have no (and probably won't in the near future) worry about this.

It is still a good idea to trap some errors, such as the default case for when the enum is changed but the switch is not updated to reflect it, however the run time errors have to be trapped from the code that calls the method.

Points of Interest

Side note: Consider these three articles for better ways of working with enumerations and particularly if you find yourself using switches.

Personally, I'll probably replace it with the Strategy Pattern.

History

  • October 2013: Version 1.0
  • October 2013: Version 1.1: Added source code download
  • August 2017: Version 1.2: Added link to C# Replacing switch(enum) flow control with Reflection article

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Engineer
France France
A hobbyist begin-again-er!

Spent a few years longer ago than I care to remember with BBC Basic, a couple of years with Delphi about 10-15 years ago with a smattering af MS Access applications along the way. Dropped out of it completely except for the occasional Excel macro.

Finally picked up the baton again with VB.Net in VS2010 and now VS 2012and have been playing around quite a bit with a few odds and sodds, learning much as I go - and long may it continue.

I don't work even close to the IT industry and probably never will, but I do enjoy it as a hobby.

Comments and Discussions

 
Questionsource code not downloadable Pin
fredatcodeproject14-Oct-13 2:01
professionalfredatcodeproject14-Oct-13 2:01 
AnswerRe: source code not downloadable Pin
Thomas Daniels14-Oct-13 6:40
mentorThomas Daniels14-Oct-13 6:40 
GeneralRe: source code not downloadable Pin
fredatcodeproject14-Oct-13 6:55
professionalfredatcodeproject14-Oct-13 6:55 
the download is ok now
thanks
GeneralRe: source code not downloadable Pin
M-Badger14-Oct-13 7:28
M-Badger14-Oct-13 7:28 
GeneralRe: source code not downloadable Pin
Thomas Daniels14-Oct-13 7:32
mentorThomas Daniels14-Oct-13 7:32 
GeneralRe: source code not downloadable Pin
M-Badger14-Oct-13 7:53
M-Badger14-Oct-13 7:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.