uid | summary |
---|---|
value-contracts |
The document provides a detailed guide on how to validate input and output values of fields, properties, or parameters using Metalama.Patterns.Contracts package in coding. It covers contract inheritance, return values, out parameters, ref parameters, and fields and properties. |
Most often, you will add contracts directly to their target field, property, or parameter using custom attributes.
Follow these simple steps:
- Add the
Metalama.Patterns.Contracts
package. - Add one of the <xref:contract-types?text=contract attributes> to the fields, properties, or parameters you wish to validate.
[!metalama-test ~/code/Metalama.Documentation.SampleCode.Contracts/Input.cs]
By default, all contracts are inherited from interfaces and virtual
or abstract
members to their implementation. This means that when you add a contract to an interface member, it will be automatically implemented in all classes implementing this interface. The same rule applies to virtual
or abstract
members.
In the following example, contracts are applied to members of the ICustomer
interface. You can observe that they are automatically implemented by the Customer
class that implements the interface.
[!metalama-test ~/code/Metalama.Documentation.SampleCode.Contracts/Inheritance.cs]
The most common use of code contracts is to validate the input data flow. This happens by default when you apply a contract to a field, property, or any parameter except out
ones. When you validate the input data flow, you are essentially being cautious and defensive against the code calling you. This is a best practice as it prevents defects of foreign components from causing unexplainable failures in your own component.
Validating the output data flow can also be useful. This is particularly beneficial when you distrust the implementation of some interface or virtual method. Therefore, it makes more sense to validate the output data flow when the constraint is applied to an interface or virtual member, and inheritance is used to enforce the constraint on implementations.
If the validation of the output data flow fails, an exception of type xref:Metalama.Patterns.Contracts.PostconditionViolationException is thrown.
To validate the return value of a method, apply the contract to the return parameter using the [return: XXX]
syntax.
In the following example, a [NotEmpty]
contract has been added to the return value of the GetCustomerName
method in the ICustomerService
interface. The CustomerService
class implements this interface, and you can observe how the return value of the GetCustomerName
method implementation is being validated by the [NotEmpty]
contract.
[!metalama-test ~/code/Metalama.Documentation.SampleCode.Contracts/ReturnValue.cs]
To validate the value of an out
parameter just before the method exits, simply apply the custom attribute to the parameter as usual.
In the following example, a [NotEmpty]
contract has been added to the out
parameter of the TryGetCustomerName
method in the ICustomerService
interface. The CustomerService
class implements this interface, and you can observe how the value of the out
parameter of the TryGetCustomerName
method implementation is being validated by the [NotEmpty]
contract.
[!metalama-test ~/code/Metalama.Documentation.SampleCode.Contracts/OutParameter.cs]
By default, only the input value of ref
parameters is validated. To change the default behavior, use the xref:Metalama.Patterns.Contracts.ContractBaseAttribute.Direction property. To validate only the output value, use the xref:Metalama.Framework.Aspects.ContractDirection.Output value. To validate both input and output values, use xref:Metalama.Framework.Aspects.ContractDirection.Both.
In the following example, a [Positive]
contract has been added to the ref
parameter of the CountWords
method of IWordCounter
. The xref:Metalama.Patterns.Contracts.ContractBaseAttribute.Direction property is set to xref:Metalama.Framework.Aspects.ContractDirection.Both so that both the input and the output value of the parameter are verified. The WordCounter
class implements the IWordCounter
interface. You can observe that the [Positive]
contract is verified both when the method enters and completes.
[!metalama-test ~/code/Metalama.Documentation.SampleCode.Contracts/RefParameter.cs]
At first glance, it may seem surprising, but fields and properties also have an input and an output flow if you consider them from the right perspective. The input flow is the assignment one, i.e., the value passed to the setter, while the output flow is the one of the getter.
By default, when a contract is applied to a field or property that has a setter, the contract validates the value passed to the setter.
Just like with ref
parameters, you can use the xref:Metalama.Patterns.Contracts.ContractBaseAttribute.Direction and set it to either xref:Metalama.Framework.Aspects.ContractDirection.Output or xref:Metalama.Framework.Aspects.ContractDirection.Both.
In the following example, we have added the [NotEmpty]
contract to two properties of the IItem
interface. The Key
property is get-only, so the contract applies to the getter return value by default. The Value
property has both a getter and a setter, so we have set the xref:Metalama.Patterns.Contracts.ContractBaseAttribute.Direction property to xref:Metalama.Framework.Aspects.ContractDirection.Both to validate both the input value and the output value.
The Item
class implements the IItem
interface. You can observe that the contracts defined on the IItem
interface are implemented in the code.
In the Item
class, the Key
property is implemented as an automatic property. It might seem surprising that the contract is still implemented in the getter instead of in the setter. The reason is to preserve the semantics of the contract: when applied to the getter, the contract promises to throw a xref:Metalama.Patterns.Contracts.PostconditionViolationException exception upon violation. Implementing the contract on the getter would change the contract. Specifically, no exception would be thrown if the property is never set.
[!metalama-test ~/code/Metalama.Documentation.SampleCode.Contracts/Property.cs]