是否可以使用方法调用或类似方法禁用对类的运行时(和编译时)检查?我在使用不变量的类时遇到问题,并将它们与动态构造实例的外部库一起使用。我希望将这些调用包装在我自己的调用中,这些调用采用可能部分构造的对象,并返回一个始终有效的调用。
例如,请考虑以下代码:
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
namespace ConsoleApp
{
class Person
{
public string Name { get; set; }
public string Email { get; set; }
public string JobTitle { get; set; }
public Person(string name, string email, string title)
{
Name = name;
Email = email;
JobTitle = title;
}
[ContractInvariantMethod]
private void ObjectInvariant()
{
Contract.Invariant(Name != null);
Contract.Invariant(JobTitle != null);
}
}
public static class ObjectBuilder
{
// Just a sample method for building an object dynamically. In my actual code, code using
// elastic search, NEST, serializion or entity framework has similar problems.
public static T BuildFromDictionary<T>(Dictionary<string, object> dict)
{
Contract.Requires(dict != null);
T result = (T)System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof (T));
foreach (var pair in dict)
{
string propName = pair.Key;
var property = typeof (T).GetProperty(propName);
property.SetValue(result, pair.Value);
}
return result;
}
}
class Program
{
public static Person CreatePerson()
{
Dictionary<string, object> personData = new Dictionary<string, object>();
personData["Name"] = "Fred";
personData["Email"] = "email@example.com";
Person person = ObjectBuilder.BuildFromDictionary<Person>(personData);
person.JobTitle = "Programmer";
return person;
}
static void Main(string[] args)
{
Person person1 = new Person("Bob", "Bob@example.com", "Hacker");
Person person = CreatePerson();
Console.WriteLine(person.Name);
}
}
}
这会正确编译,但会在property.SetValue(result, pair.Value);
行引发异常。这是因为它将调用名称设置器,并且在该阶段Email
和JobTitle
为空。
我想要做的是禁用代码部分中的合同。例如,用这样的东西替换CreatePerson
方法:
public static Person CreatePerson()
{
Dictionary<string, object> personData = new Dictionary<string, object>();
personData["Name"] = "Fred";
personData["Email"] = "email@example.com";
Person person;
Contract.DisableRunTimeChecks(() =>
{
person = ObjectBuilder.BuildFromDictionary<Person>(personData);
person.JobTitle = "Programmer";
});
Contract.CheckInvariants(person);
return person;
}
Contract.DisableRunTimeChecks
禁用对该代码块中的所有代码(以及在其中进行的任何调用)的运行时检查,并且Contract.CheckInvariants
为给定对象运行ContractInvariantMethod
。
这可能以某种方式实现,还是有另一种解决方案?
我看到的一个我不想做的解决方案是在_initialized
上引入Person
字段,并在进行任何检查之前使不变方法检查是否为真。这是因为除了一个或两个构造函数方法之外,对象应始终有效。
答案 0 :(得分:0)
问题在于定义不变量的方式。我发布了一个类似问题here的答案。请阅读问题的答案以获取详细信息,但是这里有一些简短的段落来自问题的根源:
对象不变量是指每当该对象对客户端可见时,应该在类的每个实例上保持为true的条件。它们表达了对象处于“良好”状态的条件。
第10页顶部的手册中有一些特殊的句子:
不变量在全合同符号 [CONTRACT_FULL] 上有条件地定义。在运行时检查期间,会在每个公共方法的末尾检查不变量。如果不变量在同一个类中提到了一个公共方法,那么通常在该公共方法结束时发生的不变检查将被禁用,并且仅在对该类的最外层方法调用结束时进行检查。如果由于在另一个类上调用方法而重新输入类,也会发生这种情况。
- 括号内的文字是我的;这是手动引用的编译时符号,已定义。
由于属性实际上只是
T get_MyPropertyName()
和void set_MyPropertyName(T)
的语法糖,这些句子似乎也适用于属性。查看手册,当他们显示定义对象不变量的示例时,它们显示在不变合同条件下使用私有字段。
所以这就是你要如何定义Person
类:
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
namespace ConsoleApp
{
class Person
{
private string _name;
private string _jobTitle;
// For consistency, I would recommend creating a
// private backing field for Email, too. But it's not
// strictly necessary.
public Person(string name, string email, string title)
{
// Are these pre-conditions strict enough??
// Maybe they are, but just asking.
Contract.Requires(name != null);
Contract.Requires(title != null);
_name = name;
_jobTitle = title;
Email = email;
}
[ContractInvariantMethod]
private void ObjectInvariant()
{
Contract.Invariant(_name != null);
Contract.Invariant(_jobTitle != null);
}
public string Name
{
get
{
Contract.Ensures(Contract.Result<string>() != null);
return _name;
}
set
{
Contract.Requires(value != null);
_name = value;
}
}
public string JobTitle
{
get
{
Contract.Ensures(Contract.Result<string>() != null);
return _jobTitle;
}
set
{
Contract.Requires(value != null);
_jobTitle = value;
}
}
public string Email { get; set; }
}
所以回顾一下:不变量只会在调用任何公共方法之前以及从公共方法返回时告诉消费者他们对该对象的期望是什么(在运行时)。在这种情况下,您告诉消费者,当您调用Person.Name
和Person.JobTitle
为非null
的公共方法时,以及当任何公共方法返回到再次呼叫者,Person.Name
和Person.JobTitle
将是非null
。但是,为了确保可以维护(和强制执行)这些不变量,类在获取/设置改变私有支持字段_name
的值的属性时需要说明前置条件和后置条件。和_jobTitle
。