tags:

views:

274

answers:

3

I'm trying to use the code from this article: Creating Fake Enums, but I can't figure out why it doesn't work.

This code:

Console.WriteLine(FakeEnum.One.FriendlyName);
Console.WriteLine(FakeEnum.Four.FriendlyName);

generates an exception:

System.TypeInitializationException was unhandled
  Message="The type initializer for 'FakeEnum' threw an exception."
  Source="FakeEnum1"
  TypeName="FakeEnum"
  StackTrace:
       at FakeEnum1.Program.Main(String[] args) in ..\Test\FakeEnum1\Program.cs:line 26
       at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: System.NullReferenceException
       Message="Object reference not set to an instance of an object."
       Source="FakeEnum1"
       StackTrace:
            at FakeEnum.op_Equality(FakeEnum a, FakeEnum b) in ..\Test\FakeEnum1\FakeEnum.cs:line 158
            at FakeEnum.ToString(String format) in ..\Test\FakeEnum1\FakeEnum.cs:line 31
            at FakeEnum.ToString() in ..\Test\FakeEnum1\FakeEnum.cs:line 25
            at FakeEnum..ctor(Int32 value, String friendlyName) in ..\Test\FakeEnum1\FakeEnum.cs:line 171
            at FakeEnum..ctor(Int32 value) in ..\Test\FakeEnum1\FakeEnum.cs:line 165
            at FakeEnum..cctor() in ..\Test\FakeEnum1\FakeEnum.cs:line 13

If I comment the members declared with the (int) constructor, everything else works:

public static readonly FakeEnum One = new FakeEnum(1, "One's Friendly Name");
public static readonly FakeEnum Two = new FakeEnum(2, "Two's Friendly Name");
public static readonly FakeEnum Three = new FakeEnum(3, "Three's Friendly Name");
//public static readonly FakeEnum Four = new FakeEnum(4);
//public static readonly FakeEnum Five = new FakeEnum(5);
//public static readonly FakeEnum Six = new FakeEnum(6);

Now, if I make the constructors public, the following code works just fine:

FakeEnum a = new FakeEnum(14, "1 4");
FakeEnum b = new FakeEnum(28);
Console.WriteLine(a.FriendlyName);
Console.WriteLine(b.FriendlyName);

I just run out of ideas - what am I missing, and what generates the exception when using the original code?

+6  A: 

It's this bit:

FakeEnum temp = staticField.GetValue(null) as FakeEnum;
if(temp == null) continue;

That "==" is calling the == operator, which doesn't expect the left hand side to be null:

public static bool operator == (FakeEnum a, FakeEnum b) { 
    return a.Equals(b);
}

You can fix the operator like this:

public static bool operator == (FakeEnum a, FakeEnum b) { 
    if (object.ReferenceEquals(a, b)) {
        return true;
    }
    if (object.ReferenceEquals(a, null) || object.ReferenceEquals(b, null)) {
        return false;
    }
    return a.Equals(b);
}

(Or by using object.Equals, as pointed out in another answer - doh!)

or you could change it to make Equals delegate to == instead of the other way round.

Personally I'd say this class is looking a tad hairy. It's not even clear to me why == and != need overloading or indeed why Equals needs overriding unless you really have two separate objects with the same value (e.g. due to serialization, which is going to be icky anyway).

Jon Skeet
Can you please tell me why the declaration of FakeEnum.Four triggers the exception, but declaring a new variable using the same constructor doesn't?
alexandrul
@alexandrul: Because it's looking through the fields of the enum *while the type is still being initialized*. Therefore some of those fields are null, which triggers the problem. This class is trying to be too clever for its own good, IMO.
Jon Skeet
(By the way, making the constructors public removes a large point of it being an enum - it's meant to be a *fixed set of values*.)
Jon Skeet
(just for debugging purposes)
alexandrul
+5  A: 

I haven't really tried it myself, but the == and != operations are definitely not implemented correctly. Should be:

public static bool operator == (FakeEnum a, FakeEnum b) { 
    return object.Equals(a,b);
}
public static bool operator != (FakeEnum a, FakeEnum b) { 
    return !object.Equals(a,b);
}
Grzenio
+1  A: 

object.equals calls the object's == operator (which would call the object.equals method which would call the objects (which would call ... ) ) and all you'd be left with is a stack overflow.

it calls the object's equals method to test the underlying integer value to simulate how an enum instance would behave.

Steve