在我们了解了平台的基本功能后,接下来会具体介绍MicroStation软件的二次开发语言。如果您已经非常熟悉使用开发语言进行的开发,可以跳过这一部分内容。

MicroStation的开发语言介绍

程序语言是我们跟计算机沟通的桥梁,想让计算机为我们工作,我们就要通过编程语言告诉计算机我们的想法。如果对语言不熟悉,无法准确表达我们的想法的话,计算机就无法正确的按照我们的想法工作。就好比我们跟外国人打交道时,如果对其语言不了解,只是浅显的学了几句外语的话,很容易传递一些错误的指令。
MicroStation二次开发支持多种编程语言,按照难度大小排序先后是VBA、C#、C/C++、C++/CLI,当然各种编程语言下提供的编程接口或者说是能实现的功能也是随着难易程度的提升而越来越丰富的。MicroStation中的大部分功能底层都是由C/C++来实现的,所以说大部分编程接口都是先有C/C++版本的,然后是在C/C++的这些接口基础上通过C++/CLI封装成托管编程语言的接口。只有很少一部分功能是直接由托管语言来实现的,例如EC相关的一些功能。那么我们二次开发的时候该如何选择使用哪种语言呢?这个是要根据我们的需求以及实际情况来选择的,如果您是一名设计师,没有任何编程基础或者说编程方面相关工作经验的话,最好的选择是VBA。如果实际工作中发现VBA不能实现您的需求,从一名设计师的角度来说,最好的途径是委托专业的编程人员来实现。除非您下定决心,往后要从事软件开发发面的工作,否则不建议您耗费巨大的精力去学习另外几种编程语言。而如果您的团队是一支专业的软件开发团队,那么优先选择的开发语言是C#。因为C#语言本身难度相对C/C++、C++/CLI来说会容易一点(相信使用过这些语言的人员对这几种语言各自处理字符串的能力强大一定有很深的体会吧),尤其是在实现用户界面时,C#有很强的优势,所以其开发效率相对来说是这几种语言中最高的。但是正如前面说的,难免会有一些接口是C/C++中已经有的,而C#还没有封装。所以这个时候我们的团队就需要一到两名对C/C++、C++/CLI比较熟悉的人员了,由其将C/C++的接口通过混合编程,封装成C#的接口供团队其他人员使用。

C#语言基础

C#语言介绍

C#语言是一种面向对象的语言,其语法与C/C++非常类似,但又简化了C/C++语言在类型派生等方面的用法,降低了C/C++的复杂性。使得C#语言使用更简单。不同于C/C++语言,即包含面向过程设计思想,又包含面向对象程序设计思想,C#语言是完全面向对象的,在C#中没有全局函数、全局变量,所有的函数、变量和常量都必须声明定义在类中,这样避免了命名冲突。C#语言不支持多重继承。使用过C/C++和JAVA语言的话,学习C#语言会很容易上手。
C#语言编译器将C#源代码编译为中间语言(MicroSoft Intermediate Language,MSIL)代码,生成的文件类型为.exe或者.dll。CPU不能直接执行MSIL代码,MSIL代码在运行时由通用语言运行环境(Common Language Runtime,CLR)中的既时编译器(JUST IN Time,JIT)编译为CPU可执行的二进制机器码。C#程序在CLR的运行时环境中运行,在此环境下运行使得我们的C#程序有诸多好处。首先CLR提供的垃圾回收机制,在对象实例的生命周期结束时,会负责收回对象实例所占用的内存空间。不像C/C++语言,一些动态分配的资源需要程序员负责收回。CLR提供了这种自动回收功能。其次C#语言是一种安全的语言,其默认情况下不像C/C++语言那样允许我们直接通过内存地址来操作内存。一切对内存的访问都必须通过对象实例来完成,这就防止一些恶意程序通过指针来完成一些非法的操作。而且这也避免了对内存的误操作而产生的问题。

面向对象的核心-类

面向对象的核心思想是将问题模型中的所有对象抽象定义为程序语言中的一个个类,对象具有各种各样的属性和状态,我们可以在类中声明定义各种类型的成员变量来代表这些属性和状态。除了具有各种属性以外,每种对象还有各自不同的行为,对象的这些行为可能会使得对象属性和状态发生变化。我们可以在类中声明定义各种不同的成员函数来模拟这种行为,这些成员函数可以处理类中定义的各种数据。例如,定义一个描述动物的类Animal如下:

class Animal
{
private string name;
private double weight;
private int age;

  1. public void Walk()<br /> {
  2. }
  3. public void Run()<br /> {
  4. }<br />}

我们定义了一种新的代表Animal的数据类型,其描述了Animal的属性和行为。类的声明格式如下:
属性 类修饰符 class 类名{类体}
关键字class、类名和类体是必须的,其它项是可选项。类修饰符有:new、public、protected、internal、private、abstract和sealed,我们在后边的内容种介绍这些修饰符。类体用于定义类的成员。

类成员的访问类型

类的成员变量以及成员函数有些对外部是隐藏的,只有在类的内部才能访问到的,有些是暴漏给外部程序调用本类使用的,这些可用访问权限修饰符来控制,常用的访问权限修饰符有:private(私有),public(公有)。在成员变量或者成员函数前增加访问权限修饰符,可以指定该成员变量或函数的访问权限。
私有数据成员只能被类的成员函数使用和修改,私有成员函数只能被类的其它成员函数调用。类的公有成员函数可以被类的外部程序调用,类的公有成员变量可以被类的外部程序直接访问到。公有成员函数实际就是类和外部通讯的接口,外部程序通过调用公有函数,可以影响到类的私有成员。

类的实例化

Animal类仅是一种我们新定义的数据类型,通过它可以生成Animal类的实例。用如下语句完成类的对象实例化:Animal anim=new Animal();执行此语句后会在内存中分配一块内存存放一份Animal对象的数据,然后返回此内存块的地址并赋值给变量Anim。在程序中,可以用anim.成员名访问对象的成员。

类的构造函数和析构函数

创建类的对象实例时,有时需要做一些初始化的工作。这些可以在构造函数中去完成。当用new生成类的对象实例时,类的构造函数会被执行。因此,初始化的工作放到构造函数中完成。构造函数和类名相同,没有返回值。我们可以定义Animal类的构造函数如下:

public Animal(string Name,double Weight,int Age)
{
name = Name;
weight = Weight;
age = Age;
}

当用Animal anim=new Animal(“老虎”,30,5)语句生成Animal的对象实例时,上面的构造函数会马上被执行。
类的对象实例都有其生命周期,生命周期结束时,对象实例会被撤销。此时,将自动调用析构函数。一些清除工作可放在析构函数中完成。析构函数的名字为~类名,没有返回类型,也没有任何参数。Animal类的析构函数为~ Animal()。我们在源代码中不能直接调用析构函数,CLR的垃圾回收机制会自动调用生命周期结束的对象实例的析构函数。

数据类型

C#语言的数据类型按使用方式可以分为:值类型,引用类型。

值类型和引用类型的区别

C#语言中,值类型对象实例是存储在函数调用栈(Stack)上的,赋值语句是传递变量的值。而引用类型(类就是引用类型)的对象实例,则是存储在托管堆(Managed Heap)中,堆实际上是计算机系统中的空闲内存。引用类型变量的值存储在栈(Stack)上,但栈存储的不是引用类型的对象实例,而是引用类型对象实例的引用,即地址,和指针所代表的地址不同,引用所代表的地址不能被修改,也不能转换为其它类型地址,它是引用型变量,只能引用指定类型的对象实例,引用类型变量赋值语句传递的是对象实例的地址。请看如下代码:
using System;
class ClassA
{
public int a = 0;
}
class Program
{
public static void Main()
{
fun();
}
public static void fun()
{
int val1 = 1;
int val2 = val1;
val2 = 2;
ClassA classA1 = new ClassA();
ClassA classA2 = classA1;
classA2.a = 2;
}
}
值类型的变量,当其生命周期结束时,会马上被撤销,其所占用的内存会马上被回收。上面的代码中fun函数中的val1就是这种类型的,fun函数执行完毕后,val1所占用的内存会立马被回收。而引用类型的对象实例在其生命周期结束时不会马上被撤销。CLR的垃圾回收机制会在引用类型的声明周期结束以后的某个时刻收回对象实例所占用的资源。例如,上例中引用类型的对象实例classA1和classA2是ClassA类的对象实例的引用,其指向存储在托管栈中对象实例,函数fun退出时,classA1和classA2都不存在了,在托管堆中ClassA类的对象实例所占用的医院也就会被CLR回收了。

值类型

C#语言值类型可以分为以下几种:
简单类型(Simple types)
简单类型中包括:数值类型和布尔类型(bool)。数值类型又细分为:整数类型、字符类型(char)、浮点数类型和十进制类型(decimal)。
结构类型(Struct types)
枚举类型(Enumeration types)

结构类型

结构类型和类类似,可以定义构造函数、数据成员、方法、属性等。两者的区别是结构是值类型,类是引用类型。结构不能从另外一个结构或者类派生,本身也不能被继承。结构成员也不能被protected修饰,也不能用virtual和abstract修饰结构的成员方法。结构类型不能定义析构函数。

基本类型

基本类型也是值类型,也有构造函数、数据成员、方法、属性等,因此语句int i=int.MaxValue;string s=i.ToString()是正确的。C#中的常量,后台也会为其生成值类型的实例,因此string s=13.ToString()是正确的。基本类型包括:整数类型、字符类型、布尔类型、浮点数类型、十进制类型。见下表:

System命名空间中的名字 字节数 取值范围
sbyte System.Sbyte 1 -128~127
byte System.Byte 1 0~255
short System.Int16 2 -32768~32767
ushort System.UInt16 2 0~65535
int System.Int32 4 -2147483648~2147483647
uint System.UInt32 4 0~4292967295
long System.Int64 8 -9223372036854775808~9223372036854775808
ulong System.UInt64 8 0~18446744073709551615
char System.Char 2 0~65535
float System.Single 4 3.4E-38~3.4E+38
double System.Double 8 1.7E-308~1.7E+308
bool System.Boolean (true,false)
decimal System.Decimal 16 正负 1.0×10-28 到7.9×1028之间

C#基本类型使用方法和C/C++中相应的数据类型基本一致。

枚举(Enum)类型

C#中枚举类型使用方法和C/C++中的枚举类型基本一致。枚举类型的定义如下所示:
enum Direction { East,West,South,North}
在此枚举类型Direction中,每个元素的类型为int,其中East =0,West =1,依此类推。也可以直接给枚举元素赋值。
enum Direction { East=2,West,South,North}
在此枚举中,East =2,West =0,South =1,North =3,等等。

值类型的初始值和默认构造函数

C#中所有变量都要求必须有初值,如果创建对象实例时没有赋值的话,则会被赋予默认值。对于基本类型,默认值为0。也可以在实例化的时候直接赋值,例如int i=0。所有的值类型都有默认的无参构造函数,其功能就是为该值类型赋初值为默认值。在自定义结构类型,由于已有默认的无参构造函数,所以不能再定义无参构造函数,但可以定义有参数的构造函数。

引用类型分类

C#语言中引用类型可以分为以下:类(class),接口(interface),委托(delegate)。引用类型对象实例一般用运算符new创建,用引用类型变量引用该对象实例。

object类

object类是C#中所有类型(包括值类型)的基类。任何一个类在定义的时候,如果不指定基类,则object为基类。基类的引用变量可以引用派生类的对象。因此,对一个object的变量可以赋予任何类型的值。

数组类

如果要同时操作某一类型的多个对象实例时,数组是最简单方便的类型。数组是一组类型相同的有序数据。数组按照数组名、数据元素的类型和维数来进行描述。C#语言中数组的类型是System.Array,声明一个一维的包含五个元素的整型数数组如下所示:
int[] arr=new int[5];
以上代码会生成一个数组类的对象实例,arr引用这个对象的实例。
数组可以是一维的也可以是多维的,即数组的元素还是数组。一维数组最为普遍,用的也最多。通过索引运算符我们可以访问到数组中的任意一个元素,例如arr[3]可以访问到数组中第四个元素(注意索引是从0开始的)。数组中元素的个数可以通过System.Array的Length属性获取到。

string类

C#还定义了一种专门用来操作字符串的类型: string,这个类在命名空间System中定义的,是System.String的别名。string类的定义中封装了许多方法,下面的一些语句展示了string类的一些典型用法:
字符串定义
string str=””;
str=”My”;
string str =”First”;
string str1=”My”+” “+ str+”string!”
字符串检索
string str=”aaabcdeee”;
int index=s.IndexOf(“bcd”);
字符串比较
string str1=”aaa”;
string str2=”bb”;
int result=string.Compare(str1,str2);
result =0表示两个字符串相同,result小于零,str1str2。字符串比较时区分大小写。也可用如下运算符比较字符串:
string str1=”abc”;
string str=”abc”;
string str2=”不相同”;
if(str==str1)
str2=”相同”;
判断是否为空字符串
string str=””;
string str1=”Not Empty”;
if(s.Length==0)
str1=”Empty”;
获取指定位置的子字符串或字符
string str=”abcdefg”;
string subStr=str.Substring(2,2);//substr=“cd”
char c=str[0];//c=’a’
删除指定位置的子字符串
string str=”abcdefg”;
string subStr=str.Remove(0,2);//subStr=”cdefg”;
插入字符串
string str=”abfg”;
string str1=str.Insert(2,”cdef”);//str1=”abcdefg”
替换指定位置的子字符串
string str=”abcccfg”;
string str1=str.Replace(“ccc”,”cde”);//str1=”abcdefg”
转换字符串内的大小写字母
string str=”ABCDabcd”;
string str1=str.ToLower();//str1=”abcdabcd”
string str2=str.ToUpper();//str1=”ABCDABCD”
删除首尾所有的空格
string str=” AA AA “;
str.Trim();//str=”AA AA”;

运算符

运算符分类

与C/C++语言一样,根据参加运算的运算数的个数来分类的话,C#中的运算符可以分为以下几类:
一元运算符:一元运算符参与运算的运算数只有一个,例如:-X、X++、—X等。
二元运算符:二元运算符参与运算的运算数有两个,例如:x%y。
三元运算符:三元运算符只有一个条件运算符:x? y:z。
C#语言中各种运算符见下表。

类型 操作符
初级运算符 (x) x.y f(x) a[x] x++ x— new type of sizeof checked unchecked
一元运算符 + - ! ~ ++x –x (T)x
乘除运算符 * / %
加减运算符 + -
移位运算符 << >>
关系运算符 < > <= >= is as
等式运算符 == !=
逻辑与运算符 &
逻辑异或运算符 ^
逻辑或运算符 |
条件与运算符 &&
条件或运算符 ||
条件运算符 ?:
赋值运算符 = *= /= %= += -= <<= >>= &= ^= |=

is运算符

is运算符用于在运行时检查表达式是否为指定类型。使用格式为:e is T,其中e是一个表达式,T是一个类型,该式判断e是否为T类型,返回值是一个布尔值。

typeof运算符

typeof运算符用于获得指定类型在system名字空间中定义的类型名字。

new运算符

new运算符用来创建指定类型的对象实例。

运算符的优先级

当一个表达式里边有多种运算符时,运算符的优先级控制着表达式的计算顺序。优先级高的运算符先计算,优先级低的运算符后计算。例如,表达式x+yz按照x+(yz)顺序求值,因为*运算符的优先级高于+运算符。“运算符分类”一节中的表总结了所有运算符从高到低的优先级顺序。
当相邻两个运算符优先级相同时,例如x+y-z,计算顺序由左到右执行。赋值运算符按照右接合的原则,即运算按照从右到左的顺序执行。例如x=y=z按照x=(y=z)进行求值。

流程控制

程序执行流程有三种类型:顺序控制,分支控制,循环控制。本节依次介绍这三种流程。
C#语言中流程控制语句包括:if语句、switch语句、while语句、do…while语句、for语句、foreach语句、break语句、continue语句、goto语句、return语句、异常处理语句等,其中foreach语句和异常语句是C#语言新增加控制语句。本节首先介绍一下这些语句和C语言的不同点,然后介绍C#语言新增的控制语句。

顺序控制

程序按照代码中的语句一行一行一次执行,中间没有任何判断及跳转。

分支控制

程序根据某些条件选择不同的分支执行,有三种形式,单分支,双分支,多分支。
单分支控制如下所示
If(条件表达式)
{

}
当条件表达式为ture时才会执行{}中的语句,否则就不执行。
双分支控制如下所示
if(条件表达式)
{

}
else
{

}
当条件表达式的值为true时执行if后{}中的语句,如果为false时,执行else后{}中的语句。
多分支控制如下所示
if(条件表达式)
{

}
else if(条件表达式)
{

}
else if(条件表达式)
{

}
else
{

}
多分支控制会依次判断每个分支中的条件表达式是否为true,遇到的第一个值为true的表达式时,执行if后{}中的语句。而后边的分支将不再继续判断执行。多分支语句除了使用上边这种形式以外,还可以使用switch语句,如下所示
switch(表达式)
{
case 表达式1:
代码块;
break;
case 表达式2:
代码块;
break;
……
default:
代码块;
break;
}
程序执行流程是依次比较switch后表达式的值与case后表达式值,相等时执行此case分支中的代码块。

循环控制

循环控制可以是程序在满足指定条件时,循环执行同一代码块。循环控制可以使用for语句,形式如下所示
for(循环变量初始化;循环条件;循环变量迭代)
{

}
循环条件是一个结果为boolean类型的表达式,当结果为true时,会继续执行for后的{}中的语句,每次执行完以后会执行一次循环变量迭代中的语句。除了使用for语句来实现循环控制以外,还可以使用do…while…语句,形式如下所示
do
{

}
while (条件表达式)
或者是如下形式

while(条件表达式)
do
{

}
另外C#语言中还有一种特殊的专门用来迭代集合或者容器类对象实例的语句:foreach语句,语句的格式为
foreach(类型 变量名 in 表达式)
{

}
每一次循环从数组或其它集合中逐一取出数据,赋值给指定类型的变量,该变量可以在循环语句中使用、处理,但不允许修改变量,该变量的指定类型必须和表达式所代表的数组或其它集合中的数据类型一致。对于数组,foreach语句循环顺序是从下标为0的元素开始一直到数组的最后一个元素。

异常控制

在编写程序时,有很多未知的情况我们无法预测到,例如用户输入格式不正确、内存溢出、网络资源不可用、数据库连接失败等,所有这些错误被称为异常,C#语言为我们提供了一种异常处理的机制使得我们的程序即使发生这些错误也能稳健地继续运行。C#中的异常处理语句形式如下所示
try
{

}
catch(System.Exception ex)
{

}
try代码块中的语句发生异常错误时,程序流程会调整到catch后的代码块中,catch后的ex对象实例包含了异常信息。这个对象类型必须是System.Exception类或它的派生类。

类的继承

在前边我们介绍类的时候定义了一个Animal的类,而动物有实际上又分很多种,例如小鸟,老虎,狮子等。我们当然可以从头开始重新定义一个新的Bird类来抽象表达老虎,但这样的话,如果我们想再定义一个表达狮子或者其他动物的类的话,就又有从头开始重新定义一个新的类,尽管所有动物都有很多相同的属性以及行为。面向对象的编程思想就允许我们定义一个基类,基类中包含某一类型的对象共同具有的属性和行为,而在定义每种具体的对象类型时,可以从这个基类派生每种对象类型,这样就可以实现代码的重复利用。

派生类的声明

派生类的声明如下所示:
属性 类修饰符 class 派生类名:基类名 {类体}
尽管派生类继承了基类的所有成员,但是在派生类中依然无法直接访问类型为private的成员。如果想在派生类中访问基类的成员的话,需要在基类中将这些成员定义为protected或者public类型。

base 关键字

base关键字用于从派生类中访问基类成员,它有两种用法。一种是在定义派生类的构造函数中,指明要调用的基类构造函数,根据base后的参数类型和个数,可以确定要调用基类的哪一个构造函数。另外一种用法是在派生类的成员函数中调用基类中被派生类重写的成员函数。

重写基类成员

在派生类中,通过声明定义与基类完全相同新成员,可以重写基类的同名成员,完全相同不仅指名字相同,对于成员变量的话,成员变量的类型(实际上如果名字相同,变量类型不同的话编译器是不允许的)也要相同。而成员函数的话,函数的返回类型,以及参数的个数,每个参数对应的类型也要完全相同。派生类重写基类成员不算错误,但会导致编译器发出警告。如果增加new修饰符,表示认可覆盖,编译器就不再发出警告。请注意,重写基类的同名成员,并不是移除了基类成员,只是必须用如下格式访问基类中被派生类重写的函数:base.Fun()。

类的成员

类的成员可以分为两大类:类本身所声明定义的以及从基类中继承来的。

类的成员类型

类的成员包括以下类型:
字段:即类中的变量或常量,包括静态字段、实例字段、常量和只读字段。
函数:包括静态函数和实例函数。
属性:按属性指定的get方法和Set方法对字段进行读写。属性实际上是编译器为我们生成的成员函数。
事件:代表事件本身,同时联系事件和事件处理函数。
运算符重载:采用重载运算符的方法定义类中特有的操作。
构造函数和析构函数。

类成员访问控制

C#语言中可以通过访问控制修饰符指定类成员的访问级别,访问修饰符有private、protected、public和internal四种。private修饰的成员变量只能被类内部的函数使用和修改,而函数成员只能被类内部的函数调用。派生类虽然继承了基类的私有成员,但不能访问它们。protected修饰的成员只能在类内部或者派生类中被访问。public修饰的成员则在类内部以及外部都能访问到。Internal修饰的成员,只能在同一程序集中可以访问,同一程序集一般是同一个应用(Application)或库(Library)。

类的字段和属性

一般把类或结构中定义的变量和常量叫字段。属性不是字段,编译器会为属性生成对应的函数。

静态字段、实例字段、常量和只读字段

用修饰符static声明的字段为静态字段。静态字段是属于整个类的,而不是某个对象实例的。所以无论在程序中是否生成了类的对象实例,该字段存在而且只有一个实例。必须通过类名来访问静态字段:类名.静态字段名。没有使用修饰符static修饰的字段为实例字段,每创建该类的一个对象实例,在对象实例内创建一个该字段的实例,创建它的对象被撤销,该字段对象也会被撤销,实例字段通过对象实例引用:实例名.实例字段名。用const修饰符声明的字段为常量,常量只能在声明中初始化,以后不能再修改。用readonly修饰符声明的字段为只读字段,只读字段是特殊的实例字段,它只能在字段声明中或构造函数中重新赋值,在其它任何地方都不能改变只读字段的值。

属性

属性不是字段,是类外部访问类内部字段的桥梁。属性定义了读取和修改字段的方法。C#中的属性更充分地体现了对象的封装性:不直接操作类的数据内容,而是通过属性进行访问,借助于get和set方法对属性的值进行读写。访问属性值的语法形式和访问一个字段一样,非常方便。
在我们前面距离的Animal类中,name,weight,age都是private类型的,外部无法访问到。现在我们通过属性来给外部提供访问这些数据内容的方法。如下所示:
class Animal
{
private string name;
private double weight;
private int age;

  1. public string Name<br /> {<br /> get<br /> {<br /> return name;<br /> }<br /> }
  2. public double Weight<br /> {<br /> get<br /> {<br /> return weight;<br /> }<br /> set<br /> {<br /> weight = value;<br /> }<br /> }
  3. public int Age<br /> {<br /> get<br /> {<br /> return age;<br /> }<br /> }<br /> public void Walk()<br /> {<br /> }<br /> public void Run()<br /> {<br /> }<br />}<br />在属性的访问声明中,只有set访问器表明属性的值只能进行设置而不能读出,只有get访问器表明属性的值是只读的不能改写,同时具有set访问器和get访问器表明属性的值的读写都是允许的。

类的成员函数

函数是类中用于执行计算或其它行为的成员。所有函数都必须定义在类或结构中。

函数的声明

函数的声明格式如下:
属性 方法修饰符 返回类型方法名(形参列表){方法体}
函数修饰符包括new、public、protected、internal、private、static、virtual、sealed、override、abstract和extern。返回类型可以是任何C#数据类型,也可以是void,即无返回值。形参列表的格式为:(形参类型 形参1,形参类型 形参2,…),可以有多个形参。

函数参数的种类

C#语言的函数参数有四种种类,值类型,引用类型,输出类型以及数组类型。
1. 值参数
值类型没有任何修饰符,值参数向函数传递参数时,程序给实参的值做一份拷贝,并且将此拷贝传递给该函数,被调用的函数不会修改实参的值,所以使用值参数时,可以保证实参的值是安全的。如果参数类型是引用类型,例如是类的引用变量,则拷贝中存储的也是对象的引用,所以拷贝和实参引用同一个对象,通过这个拷贝,可以修改实参所引用的对象中的数据成员。
2. 引用参数
有时在函数中,需要修改或得到函数外部的变量值, C#语言通过用引用参数实现。当用引用参数向函数传递实参时,程序将把实参的引用,即实参在内存中的地址传递给函数,函数通过实参的引用,修改或得到函数外部的变量值。引用参数以ref修饰符声明。注意在使用前,实参变量要求必须被初始化。
3. 输出参数
为了把函数的运算结果保存到外部变量,因此需要知道外部变量的引用(地址)。输出参数用于向函数传递外部变量引用(地址),所以输出参数也是引用参数,与引用参数的差别在于调用函数前无需对变量进行初始化。在函数返回后,传递的变量被认为经过了初始化。
4. 数组参数
数组参数使用params修饰,如果形参表中包含了数组参数,那么它必须是参数表中最后一个参数,数组参数只允许是一维数组。比如string[]和string[][]类型都可以作为数组型参数。最后,数组型参数不能再有ref和out修饰符。

静态函数和实例函数

用修饰符static声明的函数为静态函数,没有static修饰的函数为实例函数。静态函数与静态字段类似,是属于整个类的,不管是否创建类的对象实例,类的静态函数都可以被调用,使用格式为:类名.静态函数名。静态函数里不能调用非静态的成员字段和成员函数。只有在类的非静态成员函数中才可以访问非静态的成员字段和成员函数,使用格式为:对象实例名.函数名。非静态成员可以访问静态成员变量以及调用静态成员函数。

函数的重载

C#语言中,在同一个类中定义的函数名相同,而参数类型或参数个数不同的话,则认为是不同的函数,仅返回值不同,不能看作是不同的函数,这叫函数的重载。例如当我们求一个数的绝对值时,我们可以定义多个名字为abs的函数,如下所示:

class Math
{
public static int Abs(int x)
{
return x > 0 ? x : -x;
}
public static double Abs(double x)
{
return x > 0 ? x : -x;
}
public static long Abs(long x)
{
return x > 0 ? x : -x;
}
}
调用这些同名函数,在编译时,根据调用函数的实参类型和个数决定调用那个同名函数,计算不同类型数据的绝对值。

接口(interface)

接口的定义与用法与类类似,不同的是,接口中仅仅是对成员的声明,并不提供具体的实现。如果类或结构从一个接口派生,则这个类或结构必须实现该接口中声明的所有成员。接口和类都可以从多个接口派生。

接口的声明

接口声明格式如下所示:
属性 接口修饰符 interface 接口名:基接口{接口体}
其中,关键字interface、接口名和接口体时必须的,其它项是可选的。接口修饰符可以是new、public、protected、internal和private。例子:
public interface IMyBaseInterface
{
void Fun1(int param);
string Fun2(string param);
}
接口成员只能是方法、属性、索引指示器和事件,不能是常量、域、操作符、构造函数或析构函数,接口不能包含任何静态成员。因为接口中的成员只是声明,需要在派生类中实现,所以接口成员声明不能包含任何修饰符,接口成员默认访问方式是public。

接口的继承

接口的继承方式和类的继承类似,如下所示。
public interface IMyInterface:IMyBaseInterface
{
void Fun3(double param);
}
派生接口继承了基接口中的所有成员。接口允许多继承,一个派生接口可以没有基接口,也可以有多个基接口。在接口声明的冒号后列出被继承的接口名字,多个接口名之间用逗号分割。

类对接口的实现

前面我们已经说过,接口中的成员都只是声明,并没有实现,那么这些成员应该在哪里实现呢?答案是继承接口的类或者结构中。我们这里只介绍一下类中如何实现接口的成员,结构中对接口成员的实现与类中一样。类对接口的实现如下所示:
public class MyClass : IMyInterface
{
public void Fun1(int param)
{
Console.WriteLine(param.ToString());
}

public string Fun2(string param)<br />    {<br />        return param.ToLower();<br />    }

public void Fun3(double param)<br />    {<br />        Console.WriteLine(param.ToString());<br />    }<br />}<br />如果类实现了某个接口,类也间接地继承了该接口的所有基接口。因此,如果类从一个接口派生,则这个类负责实现该接口及该接口的所有基接口中所声明的所有成员。

委托

委托属于引用类型,它有点类似于C/C++中的函数指针。委托的声明格式如下所示:
属性集 修饰符 delegate 函数返回类型 定义的委托标识符(函数形参列表);
修饰符包括new、public、protected、internal和private。例如我们可以声明一个返回类型为int,包含一个int类型参数的函数委托MyDelegate:
public delegate int MyDelegate(int param);
声明了委托MyDelegate,可以创建MyDelegate的对象实例,用这个对象实例去指向一个静态或非静态的函数,所指向的函数返回类型必须为int类型,而且有一个int类型的参数。看下面的例子:
public delegate int MyDelegate(int param);

public class MyClass
{
public static int AddOne(int param)
{
return param + 1;
}

public static int SubtractOne(int param)<br />    {<br />        return param - 1;<br />    }

static void Main()<br />    {<br />        MyDelegate myDelegate = AddOne;<br />        int a = myDelegate(10);//a=11; <br />        myDelegate = SubtractOne;<br />        int b = myDelegate(10);//a=9;<br />    }<br />}

事件

事件驱动

在Windows操作系统中用户在界面上所做的各种操作都会触发一个消息,Windows操作系统会将这些消息发送到窗口的消息处理函数中。在C#中我们称用户的动作为事件,例如用户单击button按钮时会触发一个“Click”的事件。我们的程序可以监听这些事件,并在事件的处理函数中做各种操作来响应这个事件,这种方式叫做事件驱动。
C#中的各种用户控件中都定义了大量的事件,以及这些事件的处理函数。我们不需要监听响应所有的事件,只需要响应我们感兴趣的事件,编写对应的事件处理函数即可。

事件的声明

我们以C#语言类库中已预定义的Button组件为例,看一下事件是如何定义的。
首先我们需要先声明定义事件对应的委托类型,如下所示:
public delegate void EventHandler(object sender,EventArgs e);

其次我们在Button类的声明中定义我们的Click事件,如下所示:
public event EventHandler Click;

事件的监听和撤消

在声明定义了事件以后,就是如何监听此事件,使得我们的程序在用户完成此动作以后,我们的程序会做出响应。首先我们需要在Button所在窗口类中声明定义事件的处理函数,如下所示:
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show(“Click Event”);
}
然后我们需要在监听此事件之前通过Button的对象实例访问其Click成员,将处理函数添加到事件中去,如下所示:
button1.Click += new System.EventHandler(this.button1_Click);
之后当用户单击Button按钮时,就会马上调用我们的button1_Click函数了。如果不需要监听Click事件的话,通过如下方式可以撤销:
button1.Click -= new System.EventHandler(this.button1_Click);
此时, Click事件再次触发时,就不会再调用button1_Click函数了。

命名空间

一个项目可能包含许多不同的模块,有时可能还需要引用操作系统或开发环境提供的函数库、类库或组件库,第三方平台的函数库、类库或组件库,以及项目团队中其它小组或人员编写的程序等等。如果没有一个很好的机制将这么多模块中定义的各种对象的名字区别开的话,很容易造命对象之间命名冲突。C#语言中通过命名空间来解决命名冲突的问题。我们可以将不同的模块分别放到不同的命名空间下,这样两个模块中如果碰巧出校相同名字的类型时,我们可以通过在类型名称前加上命名空间的前缀来区分开来。

命名空间的声明

命名空间通过关键字namespace声明,属于这个命名空间的类型其声明定义都要房子此命名空间的{}中。命名空间的声明通常是在源文件using语句后的第一条语句,也可以作为成员出现在其它命名空间的声明之中,即在一个命名空间内还可以嵌套命名空间。在同一命名空间中,不允许出现相同名字的命名空间和类型。示例如下:
namespace NS
{
namespace NSSub
{
class Class1
{
void MyFun()
{
}
}
class Class2
{
void MyFun()
{
}
}
}
}
也可以采用非嵌套的语法来定义上面的命名空间,如下所示:
namespace NS.NSSub
{
class Class1
{
void MyFun()
{
}
}
class Class2
{
void MyFun()
{
}
}
}

命名空间的使用

如果需要引用其它命名空间的类或函数时,可以使用语句using,例如需要使用上一节中的Class1的话,可以通过如下代码:
using NS.NSSub;
class Class3
{ Class1 class1=new Class1();
}

unsafe代码

C/C++的强大在于其可以通过指针随意操作内存中任意位置的数据,但同时其也带来很多问题。因为指针指向的数据类型可能跟指针的类型并不一致,例如你完全可以一个double类型的变量的地址赋值一个int类型的指针,程序虽然不会出错,但显然运行结果肯定不会正确。如果你删除了一个不应该被删除的指针,比如Windows中指向主程序的指针,程序就有可能崩溃。一些恶意程序也可能通过指针更改程序的流程。因此指针的使用也会带来一些不好后果。在C#语言中取消了指针这个概念,但某些情况下我们有不得不借助与指针来实现一些需求,例如调用Win32 API,其参数很多都是指针类型的。所以在C#中如果我们在代码中将某个模块声明为不安全(unsafe)的时候,是可以使用指针的。例如可以指定一个函数为unsafe的,如下所示:
unsafe void Fun(int param)
{

}
还可以指定一小段代码是unsafe的,如下所示:
unsafe
{
int a=3;
int
p=&a;
*p=4;
}

C/C++语言基础

C语言是当今最流行的程序设计语言之一,它的功能丰富、表达力强、使用灵活方便、应用面广、目标程序高、可植入性好,既有高级语言的特点,又有低级语言的许多特点,适合作为系统描述语言,既可以用来编写系统软件,也可以用来编写应用软件。C语言诞生后,许多原来用汇编语言编写的软件,现在都可以用C语言编写了(如UNIX操作系统),而学习和适用C语言要比学习和适用汇编语言容易得多。
C语言是一种结构化语言。它层次清晰,便于按模块化方式组织程序,易于调试和维护。C语言的表现能力和处理能力极强。它不仅具有丰富的运算符和数据类型,便于实现各类复杂的数据结构。它还可以直接访问内存的物理地址,进行位(bit)一级的操作。由于C语言实现了对硬件的编程操作,因此C语言集高级语言和低级语言的功能于一体。既可用于系统软件的开发,也适合于应用软件的开发。此外,C语言还具有效率高,可移植性强等特点。因此广泛地移植到了各类各型计算机上,从而形成了多种版本的C语言。
在C的基础上,一九八三年又由贝尔实验室的Bjarne Strou-strup推出了C++。 C++进一步扩充和完善了C语言,成为一种面向 对象的程序设计语言。C++提出了一些更为深入的概念,它所支持的这些面向对象的概念容易将问题空间直接地映射到程序空间,为程序员提供了一种与传统结构程序设计不同的思维方式和编程方法。因而也增加了整个语言的复杂性,掌握起来有一定难度。
本教程虽然是以C#语言为基础讲解Mstn二次开发的,但是如前文所述,很多情况下我们可能需要调用Mstn 导出的C/C++接口来实现某些功能。所以这里强烈建议读者对C/C++语言也要有一定的了解。

C++/ CLI语言基础

C++/CLI(CLI:Common Language Infrastructure)包含了ISOC++和CLI扩展,它实现了ISOC++和.NET的无缝连接。C++/CLI支持对本地ISOC++编程和.NET托管编程的无缝集成,不是简单的混合,不仅能够像.NET调用Win32 API一样通过P/Invoke来实现函数互调,而且可以实现类型class级和ISOC++和.NET类库的相互使用,更强的是能够实现类型的混合。为了实现无缝连接,绝大多数以前的ISO C++代码编译后将得到托管代码,部分不能编译为托管IL的采用P/Invoke调用实现。代码托管,但是数据并不托管,原来ISOC++中本地堆内的数据仍位于本地堆中。
C++/CLI是一门用来代替C++托管扩展新的语言规范。重新简化了C++托管扩展的语法,提供了更好的代码可读性。C++/CLI为C++开发人员书写托管代码提供了一种非常自然的感觉,并且它提供了非托管代码到托管代码的平滑过度。一流的CLI支持—CLI特色,例如属性、碎片集合和属类得到了直接支持,此外,C++/CLI还准许将这些特色用于本地非托管的类。一流的C++类支持—C++特色,例如模板和析构函数对于拖管和非拖管类继续有效。实际上,C++/CLI是你可以”表面上”在栈或C++本地堆上声明一个.NET类型唯一的.NET语言。它在.NET与C++之间的沟壑上架起了一座桥梁。此外,C++/CLI编译器产生的可执行文件完全是可校验的。
C++/CLI包含了ISOC++和对其的.NET的扩展,看这两部分,一个是ISOC++,另一个是在.NET的扩展,可见基础就是ISOC++。是静态C++对象模型到CLI的动态组件对象编程模型的捆绑,或者说C++/CLI是ISOC++在.NET的扩展,是ISOC++和.NET的无缝结合。简而言之,它就是你如何用C++在.NET中编程,而不是C#或Visual Basic.NET。