1

I want my object invariant method to throw a specific exception. Does it make sense? Is it possible in C#?

For instance, I have the following code, including class A with invariant method and exception class E. For now class E is not participating in A...

class A {
    int x = 0, y = 1;

    [ContractInvariantMethod]
    private void YisGreaterThanX() {
        Contract.Invariant(x < y);
    }
}

class E : Exception {
}

And what I need is the following. Like Contract.Requires it would be useful to have Contract.Invariant (or may be an attribute constructor, which accepts Exception-derived class).

class A {
    int x = 0, y = 1;

    [ContractInvariantMethod]
    private void YisGreaterThanX() {
        Contract.Invariant<E>(x < y);
    }
}

class E : Exception {
}

Is it a good intention? May be my logic is wrong?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Hoborg
  • 891
  • 12
  • 21
  • 1
    `Contract.Requires` can throw exceptions to indicate bugs in the caller code. If `Contract.Invariant` throws, that pretty much always indicates a bug in your code. Can you expand on why you want to do this? –  Dec 22 '13 at 11:00
  • @hvd, thank for reply. Does it mean that one should include `Contract.Requires(x < y)` in every constructor of type `public A(int x, int y)` and every similar methods? Or does it mean that the logic of class A is corrupted?.. – Hoborg Dec 23 '13 at 07:44
  • Yes, in that case, I would definitely put that in the constructor. It's a combination of invalid arguments that's best indicated by an `ArgumentException`, rather than an instance of a class that then becomes unusable. –  Dec 23 '13 at 08:10
  • Putting it as a requirement on the constructor also helps with documentation: the code that calls the constructor knows that `x < y` is a requirement of that constructor. (For some reason, SO wouldn't let me edit my previous comment to include this even though less than five minutes had passed.) –  Dec 23 '13 at 08:16
  • Very helpful comments. But your suggestion entails code duplication (similar `Contract.Requires` in all similar methods), doesn't it? Should I write something like `Contract.Requires(Requirement(x, y));`, where `Requirement` is defined in the class A like `public Func Requirement { get { return (x, y) => x < y; } }`? – Hoborg Dec 24 '13 at 16:18
  • I wouldn't do that. The static analyser will not understand that, not will it help in documentation. Yes, there may be a little duplication, but that's because two separate concepts are closely linked. The invariant is a promise by the class to its users. The requirements are restrictions by the class on its users. Those restrictions may be needed to maintain the invariant (`x < y` in your case, where both `x` and `y` are modifiable), but there can also be restrictions without any invariants, or there can be invariants that do not rely on any restrictions beyond what C# itself provides. –  Dec 24 '13 at 20:54
  • In most cases I have seen, the invariants are sufficiently different from yours that the duplication would never be a problem. If you do have large numbers of modifiable properties that may break invariants, I can understand that you'd spend a lot of time just copying and pasting. In that case, though, I worry that you may have overly complicated classes. Do you have a concrete example where the duplication would (in your opinion) be so large that it hinders maintenance of your code? –  Dec 24 '13 at 20:58
  • Indeed I had an example. But after your advice I can agree that this is a pure example of overcomplicated logic. Thus it seems to me, that I understand your reasoning. Thank you for explanation. – Hoborg Dec 25 '13 at 18:23

1 Answers1

0

Given that we aren't supposed to be catching Contract failures, and since there isn't an overload for Contract.Invariant with an explicit specified exception, this answer is hopefully hypothetical.

So after all the disclaimers, you could hack something along the following lines, by wiring up a ContractFailed event handler:

Contract.ContractFailed += Contract_ContractFailed();

And in the handler, filter for Invariant failures, and handle the failure and re-throw your exception:

public static void Contract_ContractFailed(object sender,  ContractFailedEventArgs e)
{
    if (e.FailureKind == ContractFailureKind.Invariant)
    {
        e.SetHandled();
        throw new E(e.Message, e.OriginalException);
    }
}

Given that you can't pass much information in the Contract.Invariant definition, if you needed to parameterize the thrown exception, you would need to encode the intended exception into e.g. the bool, string Contract.Invariant overload, or use external global state, such as thread local storage.

All of these are smelly IMO. So to answer your last question, I believe throwing catchable exceptions isn't a good idea at all - your code is being called out of its designed range of state, so there is a bug / validation missing somewhere.

Edit
Noticed subsequently that handling + throwing in the ContractFailed handler is still wrapped in the internal ContractException. So you'll need to unpack, e.g.

catch(Exception ex)
{
   var myException = ex.InnerException;
   // ... do something
}
Community
  • 1
  • 1
StuartLC
  • 104,537
  • 17
  • 209
  • 285
  • Thank you for the reply. E.g., if one were using your suggested "smelly" technique, he couldn't catch an exception in the catch-block of the code `try { var a = new A(0, 0); } catch (Exception e) { var x = e.Message; }`? – Hoborg Dec 24 '13 at 17:03
  • By handling `ContractFailed`, setting `SetHandled` and then re-raising another exception, in effect it allows you to `convert` the Failure to a defined and catchable exception, so yes, you can then put a `try` / `catch` around this. This is smelly in that it defeats the purpose of `ContractException` as being 'fatal' and uncatchable. – StuartLC Dec 24 '13 at 17:18
  • But the code `try { var a = new A(0, 0); } catch (Exception e) { var x = e.Message; }` does NOT catch an exception. A debugger stops at the last brace of the method `public static void Contract_ContractFailed(object sender, ContractFailedEventArgs e)` in your answer and shows a notice about unhandled exception E. What am I missing? – Hoborg Dec 25 '13 at 18:33
  • It might just be your debugging settings in VS? i.e. it is breaking on all Ex's, not just unhandled? – StuartLC Dec 26 '13 at 08:39
  • Excuse me, I was wrong with my recent comment. This code works as it is expected, and the debugger settings are correct. Somehow I thought that a debugger shouldn't run the Exception Assistant in the case of exceptions which will be handle further. Your assumption has helped me to understand this. Thanks a lot for the answer. – Hoborg Dec 26 '13 at 15:27