C#、契约编程
何为契约式编程?
契约式编程是在.NET4.0后引入的,它是减少了大型项目成本突破性的技术。契约的思想很简单,它只是一组结果为真的表达式,如若违反契约失效。
契约式编程初探
需求:用户输入两个数做除法运算。
步骤一:新建一个类,用作对数字进行运算(Rationator.cs)
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication8 { internal class RationalNumber { private int numberator,denominator; public RationalNumber(int numberator,int denominator) { this.numberator = numberator; this.denominator = denominator; } public int division { get { return denominator / numberator; } } } }
步骤二、main方法中编写代码进行相应测试
static void Main(string[] args) { Console.Write("Please enter a number(denominator):"); string denominator = Console.ReadLine(); Console.Write("Please enter a number(numberator):"); string numberator = Console.ReadLine(); if (!denominator.IsNumberic() || !numberator.IsNumberic()) Console.WriteLine("The value entered is not a numberic value."); else { RationalNumber taionalnumber = new RationalNumber(int.Parse(numberator), int.Parse(denominator));
Console.WriteLine(taionalnumber.division); } }
编写扩展方法验证输入的是否是一个数字
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication8 { public static class ExtendActions { public static Boolean IsNumberic(this string value) { return System.Text.RegularExpressions.Regex.IsMatch(value, @"^[+-]?\d*[.]?\d*$"); } } }
如上代码不够严谨,虽然做了输入数字判断。但是现实中,分母是不能为0的,通常程序员会这样写
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication8 { internal class RationalNumber { private int numberator,denominator; public RationalNumber(int numberator,int denominator) { if (denominator == 0) throw new ArgumentException("The second argument can not be zero."); this.numberator = numberator; this.denominator = denominator; } public int division { get { return denominator / numberator; } } } }
那么,我们来看下契约式的写法
using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; using System.Text; namespace ConsoleApplication8 { internal class RationalNumber { private int numberator,denominator; public RationalNumber(int numberator,int denominator) { Contract.Requires(denominator!=0,"The second argument can not be zero."); this.numberator = numberator; this.denominator = denominator; } public int division { get { return denominator / numberator; } } } }
static void Main(string[] args) { Console.Write("Please enter a number(denominator):"); string denominator = Console.ReadLine(); Console.Write("Please enter a number(numberator):"); string numberator = Console.ReadLine(); if (!denominator.IsNumberic() || !numberator.IsNumberic()) Console.WriteLine("The value entered is not a numberic value."); else { try { RationalNumber taionalnumber = new RationalNumber(int.Parse(numberator), int.Parse(denominator)); Console.WriteLine(taionalnumber.division); } catch (Exception ex) { Console.WriteLine(ex.Message); } } }
PS: CR + 两下table可以快捷输出 :Contract.Requires
ps: 虽然契约加上了,.NET4.0之后也已经支持契约式编程,但如果想使用Contract方法实现运行时的验证,还需要单独安装一个VS插件。装好之后,去项目属性里开启运行时检查
需要注意的是,如果想要在Debug和Release Build都使用运行时验证功能,则需要在项目设置为Debug和Release编译时,分别设置打开Runtime check。
至此,运行项目,能看到和 加 if 判断然后抛异常差不多的效果。
接下来,就探讨一下前置条件是啥。契约中到底有哪些东西。
1. Assert
Assert:断言是最基本的契约,断言失败时CLR仅调用DEBUG.Assert,成功时,什么都不做。
static void Main(string[] args) { try { Console.Write("Please enter a number(denominator):"); string denominator = Console.ReadLine(); Contract.Assert(denominator.IsNumberic(), "Enter variable is not a numberic."); Console.WriteLine("denominator:{0}",denominator.ToString()); } catch (Exception ex) { Console.WriteLine(ex.Message); } }
2.Assume
Assume:假设契约,运行时验证检查和Assert一样。
PS:我感觉其实断言契约和假设契约一样,为什么会分成两个,可能就是根据便于程序员理解而已。书面意思:denominator断定它是个数字,denominator假设它是个数字
static void Main(string[] args) { try { Console.Write("Please enter a number(denominator):"); string denominator = Console.ReadLine(); Contract.Assume(denominator.IsNumberic(), "Enter variable is not a numberic."); Console.WriteLine("denominator:{0}",denominator.ToString()); } catch (Exception ex) { Console.WriteLine(ex.Message); } }
3.Requires
Requires:前置条件契约,通常用作方法参数的验证,先前已经有过介绍,这里就不阐述了。
4.Ensures
Ensures:后置条件契约,通常用作方法返回值验证。
using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; using System.Text; namespace ConsoleApplication8 { internal class RationalNumber { private int numberator,denominator; public RationalNumber(int numberator,int denominator) { Contract.Requires(denominator != 0, "The second argument can not be zero."); this.numberator = numberator; this.denominator = denominator; } public int divisions { get { Contract.Ensures(Contract.Result<int>() >2, "return value need to be greater than 2."); return denominator / numberator; } } } }
static void Main(string[] args) { Console.Write("Please enter a number(denominator):"); string denominator = Console.ReadLine(); Console.Write("Please enter a number(numberator):"); string numberator = Console.ReadLine(); if (!denominator.IsNumberic() || !numberator.IsNumberic()) Console.WriteLine("The value entered is not a numberic value."); else { try { RationalNumber taionalnumber = new RationalNumber(int.Parse(numberator), int.Parse(denominator)); Console.WriteLine(taionalnumber.divisions); } catch (Exception ex) { Console.WriteLine(ex.Message); } } }
后置条件契约,有个好处就是返回值不需要用临时变量保存了。如果没有采用契约编程,那么通常我们是这样写的
public int divisions
{
get
{
int result= denominator / numberator;
return result>2?result:0;
}
}