c# - 是否可以在数据对象中使用代码契约的不变量?

标签 c# poco code-contracts

这个问题直接源于Validating parameters properties with Code Contracts .

在那个问题中Ahmed KRAIEMStephen J. Anderson声明与对象的状态正确性有关的检查如果必须始终保持,则应放在该对象内。

但是我在实现它时遇到了一个问题:在将检查实现为代码契约的不变量之后,我无法再使用对象初始化、AutoMapper 或 EntityFramework。

问题的出现是因为它们首先使用默认的空构造函数创建一个新对象,然后填充各种属性,这会触发代码契约的不变量在运行时生成异常。

包含在 Visual Studio 单元测试中的快速示例(您需要通过 NuGet 添加 AutoMapper 才能使其工作):

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

如果您想知道为什么没有使用 public Cat(int, string, bool) 构造函数,那是为了愚弄 AutoMapper。对于这个例子,我需要一个构造函数来初始化整个对象,AutoMapper 识别它并自动使用它来初始化目标对象。然而,我们的数据对象没有这样的构造函数(而且,它们不能,因为它们可以有几十个属性)。

唯一通过的测试是 NonEmptyConstructorShouldNotThrow,其他的都(正确地)失败了。

问题的出现是因为它们首先使用默认的空构造函数创建一个新对象,然后填充各种属性,这会触发代码契约的不变量在运行时生成异常。

在使用对象初始化、AutoMapper 或 EntityFramework 时,我是否错误地使用了代码契约,或者没有办法在数据对象中实现不变量?

最佳答案

Cat 的默认构造函数中设置有效值,如下所示:

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

一切都会好起来的。

编辑: 正如 Damien_The_Unbeliever 的评论所述,对象始终 应该处于有效状态,因此在使用默认构造函数后它可能不会处于无效状态。

因此,您必须AgeName 设置有效值!

否则,默认构造函数根本没有意义,即使您计划这样做,甚至您 promise 在之后的某个时候设置您的属性。

唯一的另一种方法是让你的类成为可构建,这意味着你调用一个完成初始化并激活的方法,然后使用你的契约(Contract)检查方法.

关于c# - 是否可以在数据对象中使用代码契约的不变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20002388/

相关文章:

c# - 序列化排除类成员的 POCO

c# - asp 的值 :textbox is not being changed after written over it

javascript - 从服务器调用 JQuery 函数 (asp.net)

c# - 是否有编译器开关来关闭对 ​​C# 中泛型的支持?

c++ - 将一个进程标准输出重定向到管道会影响运行结果吗?

c# - 是否存在 Poco 到 DataSet 项目?

c# - 'C# in depth' 中的抽象类实例化

c# - 那么 C#4.0 代码契约真的可以做任何事吗?

c# - 格式化字符串的最快/最佳方法

c#-4.0 - Contract.Ensures 中的自定义方法