Do invariant assertions fit into C# programming?

2020-06-04 02:13发布

问题:

In the book coders at work, the author asks "How do you use invariants in your code". Please explain what this question means.

I saw class invariants on wiki, but the example is in Java and I am not skilled enough in Java to relate this example to C#. .NET 4.0 introduces invariance, covariance, and contravariance and is well explained here. Invariance is so broad. The authors usage of the word seems unit test related. For those that read the book, what does the author mean? Are we talking about making an assumption and simply testing the validity after the unit test?

回答1:

The word invariant doesn't mean more than something doesn't change under certain conditions. There are many different kinds of invariants. For example in physics the speed of light is invariant under lorentz-transform, i.e. it doesn't change if you change to reference frame. In programming there are many kinds of invariants too. There are class invariants which don't change over the lifetime of an object, method invariants which don't change during the life of a function,...

A class invariant is something that's always(at least at publicly observable times) true in an instance of that class.

This is in no way related to co-/contra-variance. Co-/Contra-variance describes which types can be substituted for other types with different (generic) parameters or return types. While you can call something invariant because it doesn't support Co-/Contra-variance this is a completely different kind of invariance than a class or method invariant.

For example some kind of collection might have the following invariants:

  • data != null
  • Size >= 0
  • Capacity >= 0
  • Size <= Capacity

With this class:

class MyCollection<T>
{
  private T[] data;
  private int size;

  public MyCollection()
  {
    data=new T[4];
  }

  public int Size{get{return size;}}
  public int Capacity{get{return data.Length;}}

  [ContractInvariantMethod]
  protected void ClassInvariant()
  {
    Contract.Invariant(data != null);
    Contract.Invariant(Size >= 0);
    Contract.Invariant(Capacity >= 0);
    Contract.Invariant(Size < Capacity);
  }
}

Almost every class has some invariants, but not everybody enforces them. .net 4 adds a nice way to document and assert them using code contracts.



回答2:

In this case, invariants mean the conditions that apply to parameters and that remain true throughout the life of the function/method.

From wikipedia:

In computer science, a predicate is called an invariant to a sequence of operations provided that: if the predicate is true before starting the sequence, then it is true at the end of the sequence.

In .NET, invariants can be checked/enforced using Code Contracts.



回答3:

That example in the Wiki is implementing invariants with JML, which is a very specific, exotic and perhaps well-thought-out research technology, but it isn't necessary mainstream at all. Also its not just talking about invariants but just talking about asserting a mutator did what was expected, which isn't what I think of when I think of invariants. I haven't read Coders at Work but I doubt anyone in Coders at Work used JML.

Anyway, I always thought invariants were a great way to "crash early" and keep your code from trying to do reasonable things when in fact the program state is in an unreasonable (unplanned-for) state.

A good example of an invariant in C# code might be to never give an object to N-Hibernate for saving unless that object has passed on its invariants, of which there should be many in order to prevent non-sensical data from entering the database. Let's see if I can think of any other examples...

  • Suppose you have a User class that is always supposed to have a primary email address property, then an invariant to check before Saving might be to make sure the email address field isn't empty. Otherwise, other application logic down the road that assumes email addresses exist for all users might mess up when trying to send a User an email later.

  • Suppose further that a User object "has many" Email objects, and suppose that it doesn't make any sense for an Email to exist without an owning user, then an invariant on the Email class might be to make sure the Email's reference to its user is always not null, otherwise you have an orphan Email object which could result in null pointer exceptions when you try to refer to the email's owner.

  • Suppose you're writing a GUI that is supposed to present the status of an Amazon S3 object in a conventional WPF form. An invariant on the form might be to make sure that the form is properly bound to its object before executing any event handlers on that form.

  • Suppose you're writing StackOverflow. An invariant here might be to make sure a user's reputation level is never negative, even if the user is taking reputation penalties. Negative reputation might break those pretty graphs that render experience as a function of time (unless those graphs are prepared to chart lines below the 0 y-axis, which they very may well be...).

As for when to check for invariants, you could do this at the end of any method that mutates data state. At the most aggressive and paranoid level of defensive programming, you could check an invariant before and after all methods.



回答4:

The meaning in this context is not related to co and contra variance for generics introduced in C#4, but rather in the same sense as the wikipedia article you link to. In C# this can be debug assertions (i.e. Debug.Assert(condition)) as well as the code contracts library. There is for example the ContractInvariantMethodAttribute which can be applied to a method in a class which asserts the class invariants:

[ContractInvariantMethod]
protected void ClassInvariant()
{
    Contract.Invariant(someCondition);
}