书接上回,上一篇介绍了在CAD中绘制一条直线并且在编辑栏中输出字符串,这个步骤以及代码量告诉我们,如果在二次开发的过程中,每绘制一个图形就用如此繁琐的步骤,那么效率会大大下降,本篇我们的目标是优化这个过程,将绘制直线的几个步骤做成函数,以便一行代码就可以将其调用。
所谓函数(在面向对象编程的语境下更多的被称为方法),可以理解为一个数据的加工机器,数学中的函数是给它参数之后,就会输出相应的函数值,在编程中,函数(方法),在获得了参数之后,就会进行返回,这里有可能有返回值,有可能没有返回值(以void为返回值函数,大多是在执行一个过程)。
说道这里,让我们来观察一下上一篇中的代码:
在绘制一条直线的过程中,调用了数据库、事务处理、块表、块表记录等等数据,但是无论是绘制直线还是圆,无论是绘制这一条直线还是那一条直线,这个过程都可以视为向一个数据库中添加了一个实体。所以,我们可以用函数的思维这样看待这个问题:把当前数据库和即将绘制的实体作为函数参数,函数执行之后,我们不需要返回值,但是需要完成支线绘制的过程,在C#语言中,这个函数要这样声明:
public void AddToModelSpace(Database db,Entity entity){}
其中void表示函数没有返回值,方法调用操作符(就是那组小括号)里面的数据库和实体就是参数(CAD中,实体是Entity类,直线等图形继承于Entity类,这里如果没了解过相关语法也不要紧,先按字面意思读即可,具体展开还是像上一篇一样,在挖的那个语法补充的坑里面补上)所以,整句话的意思就是,将数据库和实体传给函数,通过执行大括号里面的代码,完成一个将实体绘制于数据库中的过程,于是乎这里我们这样补全括号里面的代码,完成函数:
//函数所在的类类public class Class4{ //函数声明 public void AddToModelSpace(Database db,Entity entity) { //事务处理 using (Transaction trans=db.TransactionManager.StartTransaction()) { //获取块表 BlockTable bt = trans.GetObject(db.BlockTableId,OpenMode.ForRead) as BlockTable; //找到模型空间所对应的块表记录 BlockTableRecord btr = trans.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord; //将直线记录添加到模型空间的块表记录中 btr.AppendEntity(entity); //将直线添加进事务处理中 trans.AddNewlyCreatedDBObject(entity,true); //提交事务处理 trans.Commit(); } }}
这里说明一点,C#语言面向对象的特点非常坚决,所以任何函数都是出现在类里面的,由于这个类是Public类,所以在别的类中调用这个函数要这样:
//声明一个类的实例Class4 class4 = new Class4();//调用方法class4.AddToModelSpace(db,line);
这样一个已经简化了图形绘制过程的过程,其实还有三个问题:
1.CAD类库中的类已经够多了,我们还要再记一个,并且还要声明,这里显然又不简洁了,所以我们需要保留这个方法的功能,将他改为数据库的拓展方法,这样保留功能优化代码的过程一般称为重构(专业解释:重构就是在不改变软件系统外部行为的前提下,改善它的内部结构),这里,我们过河拆桥,使用拓展方法的方式让Class4这个类不在代码中出现(老规矩,拓展方法,以及下面出现的数组参数和异常检测这三个概念,在另一个系列里简述。这里只需要了解使用拓展方法的时候,需要将类和方法改为静态的,并且在一个参数的前面加一个this修饰符)
//函数声明public static void AddToModelSpace(this Database db,Entity entity){}//............................................................//调用方法 db.AddToModelSpace(line);
这里的方法调用仿佛这个方法是数据库的方法,这样的调用方式也是我个人理解拓展方法这个名字的来历。
2.只绘制一个实体过于片面,那么,如果有多个怎么一句话解决呢,这里使用数组参数。所谓数组参数,顾名思义,就是将数组作为参数,在参数前面加上params修饰符,并且在数据类型后面加上[]
//函数声明 public static void AddToModelSpace(this Database db, params Entity[] ents){}
函数体里面,通过循环遍历(foreach循环)数组中的每一个参数,于是乎,代码被重构成了这样子:
//函数声明public static void AddToModelSpace(this Database db, params Entity[] ents){ //事务处理 using (Transaction trans = db.TransactionManager.StartTransaction()) { //获取块表 BlockTable bt = trans.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable; //找到模型空间所对应的块表记录 BlockTableRecord btr = trans.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord; //遍历数组中的元素 foreach (var ent in ents) { //将直线记录添加到模型空间的块表记录中 btr.AppendEntity(ent); //将直线添加进事务处理中 trans.AddNewlyCreatedDBObject(ent, true); } //提交事务处理 trans.Commit(); }}
3.由于entity是所有图形的基类,所以在事务处理的过程中可能出现异常(也就是说不知道什么原因,没画上去),这里后果可能比较严重,甚至直接导致程序中断,数据丢失,所以我们通过异常检测(try…catch…语句块),来提高一下安全性。(这个问题坦诚一些要从硬币的两面去说:一方面,在开发过程中,我们会尽量减小程序坏掉的可能性,另外一方面,希望使用这种二次开发程序的朋友在画图过程中随手保存几下)。
//函数声明public static void AddToModelSpace(this Database db, params Entity[] ents){ //事务处理 using (Transaction trans = db.TransactionManager.StartTransaction()) { //获取块表 BlockTable bt = trans.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable; //找到模型空间所对应的块表记录 BlockTableRecord btr = trans.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord; //遍历数组中的元素 foreach (var ent in ents) { //将直线记录添加到模型空间的块表记录中 btr.AppendEntity(ent); //将直线添加进事务处理中 trans.AddNewlyCreatedDBObject(ent, true); } //try提交事务处理 try { trans.Commit(); } //捕捉到异常,则取消事务处理 catch (System.Exception) { trans.Abort(); } }}
至此,今日目标完成。我们通过方法以及代码的重构优化了向数据库中添加实体的过程,同时也留下了这样几个关于C#语法的坑需要填:拓展方法,数组参数,异常,循环。关于这个系列,笔者的目标是给自己留下一个备忘录的同时,告诉大家CAD的二次开发可以做什么,方便讨论和集思广益,从而写出更好的功能,也给感兴趣于这个话题的朋友一个参考,方便大家讨论。有什么建议或意见,或者错误之处,敬请指出。