9

When compiling code which uses code contracts, I have a very strange error I don't understand.

[ContractInvariantMethod]
private void ObjectInvariant()
{
    Contract.Invariant(
        this.isSubsidiary ||
        this.parentCompanyId == default(Guid));
}

fails with the following error:

Malformed contract. Found Invariant after assignment in method '<ProjectName>.ObjectInvariant'.

If the code is modified like this:

[ContractInvariantMethod]
private void ObjectInvariant()
{
    Contract.Invariant(
        this.isSubsidiary ||
        this.parentCompanyId == Guid.Empty);
        // Noticed the Guid.Empty instead of default(Guid)?
}

it compiles well.

What's wrong with my default(Guid)?

Arseni Mourzenko
  • 50,338
  • 35
  • 112
  • 199
  • As far as I Know: public static readonly Guid Empty; and default(Guid) or new Guid() is the same thing I dont know why it is not functioning here. – abhishek Aug 29 '10 at 23:38
  • I ran into this too. Curiously default(int) doesn't have the same effect. – Can Gencer Jul 22 '11 at 10:06
  • @Can Gencer: I think this is expected if you read the answer by Porges. For `default(Guid)`, the IL corresponds to `Guid something = new Guid()`, so there is a call to a method (constructor). Instead, `default(int)` will not correspond to `int something = new int()`, it makes no sense. That's why in the case of `int`, the compiler is not complaining. – Arseni Mourzenko Jul 22 '11 at 10:23
  • yes I understand that, but there is really no need for the compiler to do a new Guid() either, as it's a value type. – Can Gencer Jul 22 '11 at 10:48
  • I think there is. `Guid` is not nullable and is not a native type like `int`, so the compiler has no idea what will be the default value. Imagine you create `Customer` non-nullable class. The only way to get its default state would be to call the constructor with no parameters. – Arseni Mourzenko Jul 22 '11 at 13:03

1 Answers1

6

The IL generated for this:

Console.WriteLine("{0}, {1}", default(Guid), Guid.Empty);

is:

    .locals init (
        [0] valuetype [mscorlib]System.Guid CS$0$0000)
    L_0000: nop 
    L_0001: ldstr "{0}, {1}"
    L_0006: ldloca.s CS$0$0000
    L_0008: initobj [mscorlib]System.Guid
    L_000e: ldloc.0 
    L_000f: box [mscorlib]System.Guid
    L_0014: ldsfld valuetype [mscorlib]System.Guid [mscorlib]System.Guid::Empty
    L_0019: box [mscorlib]System.Guid
    L_001e: call void [mscorlib]System.Console::WriteLine(string, object, object)

Which corresponds to something like:

Guid CS$0$0000 = new Guid();
Console.WriteLine("{0}, {1}", CS$0$0000, Guid.Empty);

Code Contracts works directly on the IL, so it thinks you've written something like the second version. The rewriter is saying you're not allowed to assign to variables before the contracts, so it gives an error.

However, this is weird, because while this doesn't work:

var x = new Guid();
Contract.Invariant(
    this.isSubsidiary ||
    this.parentCompanyId == x);

this does, but it is clearly an "assignment before Invariant"!

var x = Guid.Empty;
Contract.Invariant(
    this.isSubsidiary ||
    this.parentCompanyId == x);

I think they actually modified the checker to allow some assignments like this (for ease of use), but that they haven't allowed all cases... whether this is intended or not is beyond my knowledge.

I'd report this on the Code Contracts forum, it may be a bug.

porges
  • 30,133
  • 4
  • 83
  • 114