📜 ⬆️ ⬇️

Contract Interfaces & Inheritance



Let's talk about the dark side of the force, namely, the problems in using contracts. About those cute little things that are carefully managed by library developers and bring such a rather big barrel of tar into a tiny honey pot.


Introduction.
')
We assume that we have read the classic document on MS Contracts, so we will immediately move on to such a handy thing as the specification of interfaces for contract programming.

http://research.microsoft.com/en-us/projects/contracts/
http://download.microsoft.com/download/C/2/7/C2715F76-F56C-4D37-9231-EF8076B7EC13/userdoc.pdf

If we have several public fields or methods, then once having set the specification for these parameters within the interface, we will be able to use a similar contract for real classes without additional scribbling. Having described the contract, you can then use it as a convenient magic wand.

For example, we create a representative of the other side:

public class TheDarkLord { public string Name { get; set; } private string SoName { get; set; } public TheDarkLord(string name, string soName) { this.Name = name; this.SoName = soName; } public string ModifyBaseName(string updatedName) { this.Name = updatedName; return this.Name; } } 


It is possible that for the sake of future dark padovanov you will need to use the described class as a base class. And, in order not to burden ourselves with unnecessary checks, we will set the specification for the public section, and also complement the check and private at the same time.

a) describe the interface
b) let's not forget to create an abstract implementation for it, which will incorporate the contract specification
c) and expand the main class with support for checking the private section

Implementation:

a) interface and reference to contract implementation

  [ContractClass(typeof(ITheDarkLordContract))] public interface ITheDarkLord { string Name { get; set; } string ModifyBaseName(string updatedName); } 


b) contract specification

  [ContractClassFor(typeof(ITheDarkLord))] public abstract class ITheDarkLordContract : ITheDarkLord { public string Name { get { Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()), "[ITheDarkLordContract] Name of Dark Lord is null or empty."); return default(string); } set { Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(value), "[ITheDarkLordContract] Name of Dark Lord is null or empty."); } } public string ModifyBaseName(string updatedName) { Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(updatedName), "[ITheDarkLordContract.ModifyBaseName] The new name of Dark Lord is null or empty."); Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()), "[ITheDarkLordContract.ModifyBaseName] The updated name of Dark Lord is null or empty."); return default(string); } } 


c) a modified key class to which we hooked the interface and check private

  public class TheDarkLord : ITheDarkLord { public string Name { get; set; } private string SoName { get; set; } [ContractInvariantMethod] private void DarkLordVerification() { Contract.Invariant(!string.IsNullOrEmpty(SoName)); } public TheDarkLord(string name, string soName) { Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(name)); Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(soName)); this.Name = name; this.SoName = soName; } public string ModifyBaseName(string updatedName) { this.Name = updatedName; return this.Name; } } 


The plot intrigue , which is the most unexpected ...

Everything we had was good up to this point. And then we did step number two - we used the described class as a derivative for:

  [ContractClass(typeof(ITheWhiteLordContract))] public interface ITheWhiteLord : ITheDarkLord { string UpdateAllNames(string name, string soName); } [ContractClassFor(typeof(ITheWhiteLord))] public abstract class ITheWhiteLordContract : ITheDarkLordContract, ITheWhiteLord { public string UpdateAllNames(string name, string soName) { return default(string); } } 


It seems everything is beautiful - created a successor for the interface, refined a new abstract class and tried to compile. After that, quite unexpectedly, they stepped on the limitation of the number of times for the library of contracts: we cannot inherit contract classes from anyone other than Object ... Clap - and the concept of inheritance for contract interfaces collapsed ...

Well, what do the developers recommend us? And they do it in a fantastic way: “take an abstract class, write in it the specification of the ancestor's methods” ...

  [ContractClass(typeof(ITheWhiteLordContract))] public interface ITheWhiteLord : ITheDarkLord { string UpdateAllNames(string name, string soName); } [ContractClassFor(typeof(ITheWhiteLord))] public abstract class ITheWhiteLordContract : ITheWhiteLord { public string Name { get; set; } public string ModifyBaseName(string updatedName) { return default(string); } public string UpdateAllNames(string name, string soName) { Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(name), "[ITheWhiteLordContract.UpdateAllNames] The new name of White Lord is null or empty."); Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(name), "[ITheWhiteLordContract.UpdateAllNames] The new soname of White Lord is null or empty."); Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()), "[ITheWhiteLordContract.UpdateAllNames] The updated name of White Lord is null or empty."); return default(string); } } 


Now imagine that in the base class we have 10-20 fields, which will have to be “dragged” up. A hierarchy may not be from one layer, but be inherited through a group of classes. And after that, the proposed recommendation is akin to mockery.

What did you get?

Options using interfaces, like points for future Injections, already work poorly. We'll have to remove inheritance in the contract interfaces and describe the calls of the objects we need as real classes.

a) we specify the contract for the descendant

  [ContractClass(typeof(ITheWhiteLordContract))] public interface ITheWhiteLord { string UpdateAllNames(string name, string soName); } [ContractClassFor(typeof(ITheWhiteLord))] public abstract class ITheWhiteLordContract : ITheWhiteLord { public string UpdateAllNames(string name, string soName) { Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(name), "[ITheWhiteLordContract.UpdateAllNames] The new name of White Lord is null or empty."); Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(name), "[ITheWhiteLordContract.UpdateAllNames] The new soname of White Lord is null or empty."); Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()), "[ITheWhiteLordContract.UpdateAllNames] The updated name of White Lord is null or empty."); return default(string); } } 


b) change the inheritance for the real class

  public class TheWhiteLord : TheDarkLord, ITheWhiteLord { public string UpdateAllNames(string name, string soName) { return this.Name; } } 


And now instead of using interfaces, we will use access to the specification of classes:

It was

  public class SpaceSheep { public ITheDarkLord AbstractLord { get; set; } } 


implementation as a class

  public class SpaceSheep { public TheDarkLord AbstractLord { get; set; } } 


In terms of system performance, we have not lost anything. If we have a TheWhiteLord class, then it is easy to inherit from the dark side, gaining access to the necessary properties. At the same time, the performance of the used frameworks operating on IOC will not change. In fact, to specify dependencies, we can operate with any implementation — interfaces, abstract classes, or real ones.

But the sediment remained. Who prevented the creators of Contracts.Library to give us the opportunity to operate with interfaces not only to test acceptable public properties, but also to build a holistic inheritance architecture. Alas…

For a snack.

Another minor inconvenience, which is desirable to remember. Specialization of private properties through ContractInvariantMethod imposes a small limitation. Fields that are analyzed in invariant methods must be specified in the constructor. Here is our class:

  public class TheDarkLord : ITheDarkLord { public string Name { get; set; } private string SoName { get; set; } [ContractInvariantMethod] private void DarkLordVerification() { Contract.Invariant(!string.IsNullOrEmpty(SoName)); } ... } 


Here is a sample of its creation:

  TheDarkLord lord = new TheDarkLord("James", "Bond"); 


If we add an empty default constructor, we get a warning. Because the invariant method after exiting the constructor will check the conditions and return Exception:

  CodeContracts: invariant is false: !string.IsNullOrEmpty(SoName) 


And a fairly convenient way to initialize class properties through a “deferred” assignment will not work:

  TheDarkLord secondLord = new TheDarkLord() { Name = "James" }; 


You can rewrite the private property through an additional variable and validation using contracts, discarding invariants, but this will increase the amount of code and make it impossible to use the powerful enough convenient post-condition verification tool ...

Ps . About the messages displayed by contracts I do not even stutter. It would Seem - who prevents to add the processor of dismantling of a call stack and name generation automatically. But - the creators are allowed to use only static strings, making it impossible for us to replace this kind of code.

  Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(name), "[ITheWhiteLordContract.UpdateAllNames] The new name of White Lord is null or empty."); 


on something similar to

  Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(name), string.Format("{0} The new name of White Lord is null or empty.",GetCurrentMethod())); 

Source: https://habr.com/ru/post/242945/


All Articles