3

Situation

I'm generating wrappers by using the ILGenerator. I use Object.Equals(Object, Object) For the implementation of the equality compare of the wrapper fields. The debugger throws a NullReferenceException with the following stack trace.

at System.Object.Equals(Object objA, Object objB)
at HIDDEN.StateTransitionWrapper.op_Equality(StateTransitionWrapper , StateTransitionWrapper )
at HIDDEN.StateTransitionWrapper.Equals(Object )
at System.Object.Equals(Object objA, Object objB)
at HIDDEN.StationEventCompositeWrapper.op_Equality(StationEventCompositeWrapper , StationEventCompositeWrapper )
at HIDDEN.StationEventCompositeWrapper.Equals(Object )
at System.Object.Equals(Object objA, Object objB)
at HIDDEN.CompareResult..ctor(Object object0, Object object1, String fieldName) 
....

Object.Equals(Object, Object) - Disassembly

public static bool Equals(object objA, object objB)
{
    return objA == objB || (objA != null && objB != null && objA.Equals(objB));
}

As you can see in the disassembly it's impossible that any NullReferenceException can occur because it won't reach the part where the method call is.

Possible problems

As i said the hole code is generated by using the ILGenerator and i think this could the only possible source for the error. The caller has only value types inside, so it's not even possible that the object is null.

Calling IL-Code

IL_0040: ldarg.0
IL_0041: call instance valuetype [HIDDEN]HIDDEN.StationStateType HIDDEN.StateTransitionWrapper::get_StationState()
IL_0046: box [mscorlib]System.Object
IL_004b: ldarg.1
IL_004c: call instance valuetype [HIDDEN]HIDDEN.StationStateType HIDDEN.StateTransitionWrapper::get_StationState()
IL_0051: box [mscorlib]System.Object
IL_0056: call bool [mscorlib]System.Object::Equals(object, object)
Felix K.
  • 6,201
  • 2
  • 38
  • 71
  • How is the equality operator implemented for the type of the objects used? – ken2k Feb 28 '12 at 16:37
  • @ken2k Both objects in StateTransitionWrapper are value types. – Felix K. Feb 28 '12 at 16:40
  • 1
    Is StateTransitionWrapper a value type? – Andre Loker Feb 28 '12 at 16:41
  • @AndreLoker No, its a class. You need to know that `HIDDEN.StateTransitionWrapper.op_Equality(StateTransitionWrapper , StateTransitionWrapper )` successful calls the `Object.Equals(Object, Object)`-method, so the error happens there. – Felix K. Feb 28 '12 at 16:44
  • @FelixK. I was wondering if one of the StateTransitionWrapper instances could be null and for some reason (inlining, error in the generated IL code etc.) only the stack trace isn't correct. – Andre Loker Feb 28 '12 at 16:46
  • Impossible to get a NullRef in Object.Equals. Its even shown in example on MSDN. Sure that stacktrace is right? Do we miss a stack frame? – BlueM Feb 28 '12 at 16:47
  • @AndreLoker I'm checking the references before calling the Object.Equals method. – Felix K. Feb 28 '12 at 16:47
  • MSDN example: Object.Equals("Tom", "Carol") => False Object.Equals("Tom", "Tom") => True Object.Equals(null, "Tom") => False Object.Equals("Carol", null) => False Object.Equals(null, null) => True – BlueM Feb 28 '12 at 16:48
  • @BlueM I know, but it happens. I don't know the reason but it happens 100% at Object.Equals. I checked the stacktrace about 10 times because i can't belive it. Anyway i'm comparing value types not reference. They should never be null. – Felix K. Feb 28 '12 at 16:50
  • Is it possible that this statement `(objA != null && objB != null && objA.Equals(objB));` is processing in a different order than what you expect. I.E. Is it trying to evaluate objA.Equals(objB) before determining that objA is not null? – EtherDragon Feb 28 '12 at 17:30
  • 2
    Its predetermined by the language which order is taken. Its defined in the specification. There is no random processing of that line. – BlueM Feb 28 '12 at 18:04
  • Does `StationStateType` override the `Equals` method? – Mike Cowan Feb 28 '12 at 18:26

2 Answers2

3

Shouldn't the box instruction specify the type that you're trying to box?

For example, shouldn't you be using...

box System.Int32  // or whatever

...rather than...

box System.Object
LukeH
  • 263,068
  • 57
  • 365
  • 409
  • Not if you are casting to a object to match the `Object.Equal(Object, Object)` which requires 2 objects. – Felix K. Feb 28 '12 at 19:25
  • 1
    I believe @LukeH is correct. The IL `box [HIDDEN]HIDDEN.StationStateType` would return an `object`. By using `box System.Object` you're saying "box this object to an object". I think that strips the actual type of the value being boxed. I don't know why that would result in the exception though. – Mike Cowan Feb 28 '12 at 19:41
  • You are correct, even when Mike's answer is describing the behaviour a little bit better i give you the credits. – Felix K. Feb 29 '12 at 09:23
2

The box typeTok opcode (ECMA-355 Partition III, section 4.2) takes val from the stack and converts it to obj. If typeTok is a reference type, the box instruction returns val unchanged [emphasis added] as obj. When typeTok is a value type (at least a non-nullable one), on the other hand, box creates a new object and copies the data from val into the new object.

As @LukeH points out, the IL above is using the command box [mscorlib]System.Object when it should use box [HIDDEN]HIDDEN.StationStateType. The latter will still return an object which will be valid for the Object.Equals(Object, Object) call. I believe the current call is returning an invalid object that is causing the NullReferenceException.

Mike Cowan
  • 919
  • 5
  • 11