Reputation: 21280
My main question is what to do with those Exceptions that I throw ?
For example:
Contract.Requires(foo != null);
What to do in uper levels of the function call with this thrown exception ?
Should I ignore it , and just when I see it know that something wrong with my design and fix it ?
But what would happen in deployment if I deploy without the Contracts and I will get an argument that is foo == null ,and my logic has no clue how to deal with argument like this .Then everything will crash .
Could someone explain how to deal with all those scenarios ?
Thanks
Upvotes: 4
Views: 3193
Reputation: 21
I've been looking around too for a solution on how to catch exceptions thrown by contract conditions. It is always a good idea to explicitly throw the exceptions that can occur. Wether you want to catch them so your code doesn't halt with a nice big bang;depends on what data is validated. I also use contracts for user input validation. With contract preconditions you can enforce user input to conform to certain requirements (like not null or empty string).Second, you can use contracts to validate your own internal code (especially calculations) and enforce not only parameter input being valid but also the result of calculations being valid.
It is possible to catch exceptions thrown by contract conditions; simply put the calling code inside a try-catch block and explicitly catch the type of exception(s) that your condition(s) will throw. I would only do that with user input validation. Because when contract conditions set up to not only verify parameters but also essential code logic throw an error; something might be wrong with your code logic rather than the parameter values. In that case it is better to halt the program completely. But if you want your program to terminate in a more controlled way, you can catch them though. It is then up to you to verify if it is safe to let the program continue or not.
And I found out it is also possible to check for null references on events (at least created by yourself). I used it in my own sample code that also catches the contract thrown error. You need to pass the event or the object that calls the event as an additional parameter to access the event. Following code is part of what I have in one of my classes:
public delegate void Transactie(Rekening rekening);//signature for events
public Transactie RekeningUittreksel;
public Transactie NegatiefSaldo;
public void Storten(decimal bedrag,Transactie actie)
{
Contract.Requires<NullReferenceException>(actie!=null,"\n\nNo event listeners have been added yet!\n\n");
VorigSaldo = Saldo;
Saldo += bedrag;
RekeningUittreksel(this);
}
public void Afhalen(decimal bedrag,Transactie actie,Transactie actie2)
{
Contract.Requires<NullReferenceException>(actie!=null,"\n\nNo event listeners have been added yet!\n\n");
Contract.Requires<NullReferenceException>(actie2 != null, "\n\nNo event listeners have been added yet!\n\n");
VorigSaldo = Saldo;
if (bedrag <= Saldo)
{
Saldo -= bedrag;
RekeningUittreksel(this);
}
else
{
NegatiefSaldo(this);
}
}
Next is part of the program main method. I commented out the lines where I add event listeners so the contract rules defined above will throw the nullreference exception. This is how to catch them without terminating with a big bang:
//mijnZichtrekening.RekeningUittreksel += pietjePek.ToonUittreksel;
//mijnZichtrekening.NegatiefSaldo += pietjePek.ToonNegatief;
try
{
mijnZichtrekening.Storten(50m, mijnZichtrekening.RekeningUittreksel);
}
catch (NullReferenceException ex)
{
Console.WriteLine(ex);
}
try
{
mijnZichtrekening.Afhalen(100m, mijnZichtrekening.RekeningUittreksel, mijnZichtrekening.NegatiefSaldo);
}
catch(NullReferenceException ex)
{
Console.WriteLine(ex);
}
I rewrote some code a bit to perform null reference checking on events by using the new .NET 4.5 contract abbreviators:
public void Afhalen(decimal bedrag)
{
NegatiefSaldoHasListeners(this.RekeningUittreksel, this.NegatiefSaldo);//calls the contract abbreviator with delegate type parameters to check for Nullreference
VorigSaldo = Saldo;
if (bedrag <= Saldo)
{
Saldo -= bedrag;
RekeningUittreksel(this);
}
else
{
NegatiefSaldo(this);
}
}
public void Storten(decimal bedrag)
{
UittrekselHasListeners(this.RekeningUittreksel);//calls the contract abbreviator with a delegate type (event) parameter to check for Nullreference
VorigSaldo = Saldo;
Saldo += bedrag;
RekeningUittreksel(this);
}
public virtual void Afbeelden()
{
Console.WriteLine("Rekeningnr: {0:0000 0000 0000 0000}",Nummer);
Console.WriteLine("Saldo: {0}",Saldo);
Console.WriteLine("Creatiedatum: {0:dd-MM-yyyy}",CreatieDatum);
}
[ContractAbbreviator]
public void CheckArgs(string nummer, Klant eigenaar)
{
Contract.Requires<ArgumentException>(!String.IsNullOrWhiteSpace(nummer), "Geen nummer ingevuld!");
Contract.Requires<FormatException>(nummer.Trim().Length == 16,"Ongeldig aantal tekens ingevoerd!");
Contract.Requires<ArgumentException>(!String.IsNullOrWhiteSpace(eigenaar.ToString()), "Eigenaar niet opgegeven!");
}
[ContractAbbreviator]
public void UittrekselHasListeners(Transactie actie)
{
Contract.Requires<NullReferenceException>(actie != null, "\n\nGeen event listener toegewezen!\n\n");
}
[ContractAbbreviator]
public void NegatiefSaldoHasListeners(Transactie actie,Transactie actie2)
{
Contract.Requires<NullReferenceException>(actie != null, "\n\nGeen event listener toegewezen!\n\n");
Contract.Requires<NullReferenceException>(actie2 != null, "\n\nGeen event listener toegewezen!\n\n");
}
Upvotes: 0
Reputation: 1034
In addition to Pavel Gatilov's answer, remember that you can always throw specific exceptions for preconditions
Contract.Requires<ArgumentException>(foo != null, "foo");
These can be caught and handled by the user, and can provide them with a means of handling invalid input eg: by showing a warning to the user if he input some invalid data.
Upvotes: 2
Reputation: 106836
Code Contracts allow you to more precisely declare what arguments your method accepts and also what it returns (preconditions and postconditions). Instead of having a function that accepts a string (any string) you can declare that the string should be non-null, have a length greater than 10, be entirely made up of upper case characters etc.
If the caller doesn't adhere to the contract it is an error and should be reported as such (e.g. an exception should be thrown). However, putting Contract.Requires()
statements in your source doesn't generate any actual IL code. You have to run the Code Contracts rewriter to postprocess your code. This will insert contracts check into the final IL and these checks will throw exceptions if the contract is not adhered to.
You can also use the Code Contracts static checker to prove that contracts are enforced throughout your code. If that is true you can instruct the rewriter to not insert checks as you have already proven that contracts are always adhered to. With a public API you cannot do that because the static checker don't know how your code will be called. However, if you have declare code contracts on your public API your caller can use the static checker to verify that his code is correct.
So to answer your question, you should expect your caller to adhere to your contract. You should use the rewriter to insert checks and fail in a controlled maner if in fact the caller doesn't adhere to your contract.
MSDN Magazine has an article about Code Contracts that is a good starting point for learning about the concepts.
Upvotes: 3
Reputation: 7661
My main question is what to do with those Exceptions that I throw?
First of all, you technically cannot catch these exceptions. All methods except Contract.Requires<TExc>()
throw a System.Diagnostics.Contracts.__ContractsRuntime.ContractException
which is embedded into your assembly and is private. In order to catch it you'd have to catch all exceptions which is the worst thing you could do.
Contracts as well as assertions are conditions that must always be true. If they are not, then the program is in a state that it has not been designed for, and you cannot be sure if it can continue safely. You can think of contracts as of extensions to the language. You don't expect a .NET program to let you violate type safety in 'a special case', do you? The same is true for contracts.
What to do in uper levels of the function call with this thrown exception?
The whole idea of contracts is enforcing the caller to check before calling a method with contracts. And if the caller doesn't check and does something wrong - it must be fixed. I mean: if you have a method with Contract.Requires(arg != null)
, well, then don't call it if you have a null
value.
Another question is 'should you leave all contracts in released bits or not?' From the safety position, you'd better keep them all.
If your code doesn't expect some values but it gets them, the only absolutely safe decision is halting the current operation with an error. You cannot be sure that if you ignore your contract you won't corrupt data or do other bad things. Of course, you need some degree of granularity to let your program continue in a safe state instead of terminating with a big bang, although in some cases termination is required.
Should I ignore it , and just when I see it know that something wrong with my design and fix it?
If you release your software and discover that there is a use case that doesn't work because of a contract failure, it probably wouldn't work even if there were no contracts - you just haven't thought it over and have to do some extra work to support it. You should worry about carefully designing all use cases and carrying out thorough QA to avoid this. Contracts have no relation to these issues.
But what would happen in deployment if I deploy without the Contracts and I will get an argument that is foo == null, and my logic has no clue how to deal with argument like this. Then everything will crash.
This is another reason for leaving the contracts in place. It's better to have a crash in a pre-designed place than somewhere you don't expect it to happen.
Perhaps, the only significant reason to remove some contracts is performance: checking invariants after each method may be very costly.
Upvotes: 18
Reputation: 29956
To take your concrete example, the contract is communicating that the argument to the containing method should not be null (and ideally the documentation of the method would also tell you this). When writing consuming code, you should recognize this fact, and if you do not have a non-null value available to pass as the argument to to the method then you should not call the method. It is then up to the consuming code to decide what to do in such a circumstance. It could either take an alternative execution path or throw an exception. As @sll says, the choice of action in this circumstance is completely dependent on the logic required by the needs of your application.
Upvotes: 0