4

This question stems directly from Validating parameters properties with Code Contracts.

In that question both Ahmed KRAIEM and Stephen J. Anderson state that the checks pertaining to an object's state correctness should be placed within that object if they must always hold.

However I'm encountering an issue implementing it: after implementing the checks as Code Contracts' invariants, I'm no longer able to use object initialization, AutoMapper or EntityFramework.

The problem arise because all of them first create a new object using the default empty constructor and then fill the various properties and this is triggering the Code Contracts' invariants generating exceptions at runtime.

A quick example wrapped up in a Visual Studio unit test (you need to add AutoMapper through NuGet to make it work):

namespace Playground.Sandbox
{
    using System.Diagnostics.Contracts;

    using AutoMapper;

    using Microsoft.VisualStudio.TestTools.UnitTesting;

    [TestClass]
    public class ContractTest
    {
        private const int CatAge = 5;
        private const string CatName = "Tama";
        private const int DogAge = 10;
        private const string DogName = "Poochi";

        static ContractTest()
        {
            Mapper.CreateMap<Dog, Cat>();
        }

        [TestMethod]
        public void EmptyConstructorShouldThrow()
        {
            var cat = new Cat();

            Assert.AreEqual(default(int), cat.Age);
            Assert.AreEqual(default(string), cat.Name);
        }

        [TestMethod]
        public void NonEmptyConstructorShouldNotThrow()
        {
            var cat = new Cat(CatAge, CatName, true);

            Assert.AreEqual(CatAge, cat.Age);
            Assert.AreEqual(CatName, cat.Name);
        }

        [TestMethod]
        public void ObjectInitializerShouldThrow()
        {
            var cat = new Cat { Age = CatAge, Name = CatName };

            Assert.AreEqual(CatAge, cat.Age);
            Assert.AreEqual(CatName, cat.Name);
        }

        [TestMethod]
        public void AutoMapperConversionShouldThrow()
        {
            var dog = new Dog { Age = DogAge, Name = DogName };
            var cat = Mapper.Map<Dog, Cat>(dog);

            Assert.AreEqual(DogAge, cat.Age);
            Assert.AreEqual(DogName, cat.Name);
        }

        private class Cat
        {
            public Cat()
            {
            }

            public Cat(int age, string name, bool doesMeow)
            {
                this.Age = age;
                this.Name = name;
            }

            public int Age { get; set; }

            public string Name { get; set; }

            [ContractInvariantMethod]
            private void ObjectInvariant()
            {
                Contract.Invariant(this.Age > 0);
                Contract.Invariant(!string.IsNullOrWhiteSpace(this.Name));
            }
        }

        private class Dog
        {
            public int Age { get; set; }

            public string Name { get; set; }
        }
    }
}

If you're wondering why there is the public Cat(int, string, bool) constructor if it's not used, is to fool AutoMapper. For this example I need a constructor to initialize the whole object, and AutoMapper recognize it and automatically use it to initialize the destination object. However, our data objects do not have such constructors (moreover, they couldn't, as they can have dozens of properties).

The only passing test is NonEmptyConstructorShouldNotThrow, the others are all (correctly) failing.

The problem arise because all of them first create a new object using the default empty constructor and then fill the various properties and this is triggering the Code Contracts' invariants generating exceptions at runtime.

Am I using Code Contracts incorrectly or there is no way to implement invariants in data objects when using object initialization, AutoMapper or EntityFramework?

Community
  • 1
  • 1
Albireo
  • 10,977
  • 13
  • 62
  • 96
  • 2
    Well, by definition, the invariant is meant to always hold. And you've already identified places where the invariant *will* not hold (after empty construction and before properties are assigned). And, given the nature of these objects, they have publicly settable properties and no knowledge of when pseudo-construction is complete - so those same properties may be used to invalidate the invariant even after its been established. – Damien_The_Unbeliever Nov 15 '13 at 14:00

3 Answers3

1

In the default constructor of Cat set valid values, like so:

public Cat()
{
    Age = 1;
    Name = "Cat";
}

and everything will be fine.

Edit: As stated in the comment from Damien_The_Unbeliever, the object always should be in a valid state, so it may not be in an invalid state after use of the default constructor.

Therefore you must set valid values for Age and Name!

Otherwise the default constructor makes no sense at all, even when you plan to, or more even you promise to set up your properties somewhen after.

The only other way would be to to make your class kind of buildable , means you call a method that finishes initialization and activates and uses your contract checking method after that.

abto
  • 1,583
  • 1
  • 12
  • 31
  • I don't think it will be fine, it's a terrible kludge. 1) Introducing a default often makes no sense: while there may be a default state for a door there is no "default" age for a cat. 2) It only shifts the problem: the invariant may be there to ensure the property won't be set to the default value. 3) The default values have to be chosen arbitrarily: if Item.Price must always be greater than zero, what do you use? 42.24? 4) What happens if someones forgets to "override" the default? Instead of a null in Item.Name which would upset the invariant you now have "Hammer" which is a valid value. – Albireo Nov 15 '13 at 15:07
  • This is right, but if you want to keep your object sane all the time, you **have to** provide valid default values. What if you use the object right after construction? Should it not be in a valid state? The given example renders the default constructor useless, because you never come past the needed contract. Try yourself, you **need** to set default values in your default constructor, because otherwise you end with an allways unusable object at all! – abto Nov 15 '13 at 15:15
0

There are numerous situations where it would have been valuable to start the invariant checking first after object initialization.

Unfortunately, I've not stumbled upon a great solution here, so then it comes back to selecting the better of multiple bad solutions. My favoured approach is this:

class Cat
{
    private bool _initialized;
    public void Initialized()
    {
        _initialized = true;
    }

    [Pure]
    private bool IsInValidState()
    {
        // Disregard Invariant checking until the Initialized() method has been called
        if (!_initialized) return true;

        // Check the state of the class
        if (this.Age <= 0) return false;
        if (string.IsNullOrWhiteSpace(this.Name)) return false;

        return true;
    }

    public int Age { get; set; }

    public string Name { get; set; }

    [ContractInvariantMethod]
    private void ObjectInvariant()
    {
        Contract.Invariant(IsInValidState());
    }
}

You can now do object initialization, but you have to call the Initialized() method to enable the invariant checking:

var cat = new Cat() {Age = 10, Name = "Foo"};
cat.Initialized();
Gedde
  • 1,150
  • 10
  • 19
0

Your implementation is a violation of ObjectInvariant CodeContract, because your contract says that at any time, your object must have those properties set to valid values and right after construction, that is not true. But also, it is a bad API design to have callers use your object in an undefined state. In general, I will not allow a default constructor, if the default state of the object is not a valid state. I'll rather have the constructor with minimum set of arguments required to properly construct an object or initialize the object with (valid) default arguments for its members which are being checked by the contract.

JeanP
  • 41
  • 4