Can I use Code Contracts invariants in data objects?

This question is directly related to Validating parameter properties using code contracts .

In this matter, Ahmed KRAIEM and Stephen J. Anderson argue that checks relating to an object's correct position should be placed inside this object if they should always be held.

However, I encounter the problem of its implementation: after implementing checks as Code Contracts invariants, I can no longer use object initialization, AutoMapper or EntityFramework.

The problem arises because they all first create a new object using the empty default constructor, and then fill in various properties, and this causes Code Contracts invariants that throw exceptions at runtime.

A quick example completed in the Visual Studio unit test (you need to add AutoMapper via NuGet for it to 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 are wondering why a constructor exists public Cat(int, string, bool), if it is not used, you need to trick AutoMapper. For this example, I need a constructor to initialize the entire object, and AutoMapper recognizes it and automatically uses it to initialize the target. However, our data objects do not have such constructors (moreover, they could not, because they can have dozens of properties).

NonEmptyConstructorShouldNotThrow, () .

- , , , , Code Contracts, .

, AutoMapper EntityFramework?

+4
3

Cat , :

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

.

Edit: Damien_The_Unbeliever, , .

Age Name!

, .

- , , , , .

+1

, .

, , . :

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());
    }
}

, Initialized(), :

var cat = new Cat() {Age = 10, Name = "Foo"};
cat.Initialized();
0

ObjectInvariant CodeContract, , , , - . , API, undefined. , , . , () , .

0

All Articles