You could use the idea of popsicle immutability and do what one might call popsicle validity: in other words, although an object is not valid at all times, once all of its properties have been set then it becomes valid and stays that way.
That way, you can do the validity checking in the objects containing the data and simplify the contracts on the code that uses those objects to simply thing != null && thing.IsValid()
.
Here is some code to demonstrate this approach. The static checker still needs some help to prove that a
is valid because its properties are set independently, but that's probably a check you would want to do on your object anyway after it is constructed by reflection.
internal class Program
{
private static void Main()
{
var c = new Category();
c.Name = "Some category";
var categoryRepository = new CategoryRepository();
categoryRepository.Add(c);
var a = new Article();
a.Category = c;
a.Content = "Some content";
a.Title = "Some title";
var repository = new ArticleRepository();
// give the static checker a helping hand
// we don't want to proceed if a is not valid anyway
if (!a.IsValid)
{
throw new InvalidOperationException("Hard to check statically");
// alternatively, do "Contract.Assume(a.IsValid)"
}
repository.Add(a);
Console.WriteLine("Done");
}
}
public class Category
{
private bool _isValid;
public bool IsValid
{
get { return _isValid; }
}
private string _name;
public string Name {
get { return _name; }
set
{
Contract.Requires(!string.IsNullOrEmpty(value));
Contract.Ensures(IsValid);
_name = value;
_isValid = true;
}
}
[ContractInvariantMethod]
void Invariant()
{
Contract.Invariant(!_isValid || !string.IsNullOrEmpty(_name));
}
}
public class Article
{
private bool _isValid;
public bool IsValid
{
get { return _isValid; }
}
private string _title;
public string Title
{
get { return _title; }
set
{
Contract.Requires(!string.IsNullOrEmpty(value));
_title = value;
CheckIsValid();
}
}
private string _content;
public string Content
{
get { return _content; }
set
{
Contract.Requires(!string.IsNullOrEmpty(value));
_content = value;
CheckIsValid();
}
}
private Category _category;
public Category Category {
get { return _category; }
set
{
Contract.Requires(value != null);
Contract.Requires(value.IsValid);
_category = value;
CheckIsValid();
}
}
private void CheckIsValid()
{
if (!_isValid)
{
if (!string.IsNullOrEmpty(_title) &&
!string.IsNullOrEmpty(_content) &&
_category != null &&
_category.IsValid)
{
_isValid = true;
}
}
}
[ContractInvariantMethod]
void Invariant()
{
Contract.Invariant(
!_isValid ||
(!string.IsNullOrEmpty(_title) &&
!string.IsNullOrEmpty(_content) &&
_category != null &&
_category.IsValid));
}
}
public class CategoryRepository
{
private readonly List<Category> _categories = new List<Category>();
public void Add(Category category)
{
Contract.Requires(category != null);
Contract.Requires(category.IsValid);
Contract.Ensures(category.IsValid);
_categories.Add(category);
}
}
public class ArticleRepository
{
private readonly List<Article> _articles = new List<Article>();
public void Add(Article article)
{
Contract.Requires(article != null);
Contract.Requires(article.IsValid);
Contract.Ensures(article.IsValid);
_articles.Add(article);
}
}