【转载内容】
在程序中,进行类型转换是常见的事,C#支持基本的强制类型转换方法,例如:
Object obj1 = new NewType();
NewType newValue = (NewType)obj1;
这样强制转换的时候,这个过程是不安全的,因此需要用try-catch语句进行保护,这样一来,比较安全的代码方式应如下所示:
Object obj1 = new NewType();
NewType newValue = null;
try {
newValue = (NewType)obj1;
}
catch (Exception err){
MessageBox.Show(err.Message);
}
但是上面的写法在C#中已是过时的写法,也是比较低效的写法,比较高效且时尚的写法是用as操作符,如下:
Object obj1 = new NewType();
NewTYpe newValue = obj1 as NewType;
安全性:
- as操作符不会做过多的转换操作,当需要转化对象的类型属于转换目标类型或者转换目标类型的派生类型时,那么此转换操作才能成功,而且并不产生新的对象【当不成功的时候,会返回null】。因此用as进行类型转换是安全的。
效率:
- 当用as操作符进行类型转换的时候,首先判断当前对象的类型,当类型满足要求后才进行转换,而传统的类型转换方式,是用当前对象直接去转换,而且为了保护转换成功,要加上try-catch,所以,相对来说,as效率高一点。
需要注意的是,不管是传统的还是as操作符进行类型转换之后,在使用之前,需要进行判断转换是否成功,如下:
if(newValue != null)
{
//Work with the object named “newValue“
}
但是,使用as操作符要注意以下几点:
1、不用在类型之间进行类型转化,即如下编写就会出现编译错误。
NewType newValue = new NewType();
NewTYpe1 newValue = newValue as NewTYpe1;
2、不能应用在值类型数据,即不能如下写(也会出现编译错误)
Object obj1 = 11;
int nValue = obj1 as int;
对于1.,可以用传统的类型转换方式完成:
NewTypeOne newTestOne = new NewTypeOne();
NewTypeTwo newTestTwo = (NewTypeTwo)newTestOne;
要想使上面的操作正确完成,在原有类型中增加类型转换操作符函数,即需要完成类似如下的代码:
public calss NewTypeOne
{
public static explicit operator NewTypeTwo( NewTypeOne obj1)
{
//Convert object into new type
}
}
对于2,在C#中可以使用is操作符,再加上老式的类型转换操作,就可以安全完成转换,要完成如上操作,正确的写法如下:
Object obj1 = 11;
if(objTest is int )
{
int nValue = (int)obj1;
}
在C#中提供的很好的类型转换方式总结为:
Object => 已知引用类型——使用as操作符完成;
Object => 已知值类型——先使用is操作符来进行判断,再用类型强转换方式进行转换;
已知引用类型之间转换——首先需要相应类型提供转换函数,再用类型强转换方式进行转换;
已知值类型之间转换——最好使用系统提供的Conver类所涉及的静态方法。
【原创内容】
上面所说的内容其实还不完整;
最近看了看一个外国的教学视频,里面有一个地方说到了使用as把对象进行转换:
请看截图,我将慢慢分析:
这里的代码主要是选中一个元素的边缘;视频里说如果我们不是想提取选中的边缘的信息,而是想提取边缘的这个对象本身,就要用到上图中选中的方法;
随后视频中演示了如何查询API手册,我们看到此方法:
这个方法返回类型是
GeometryObject
; 我们点击这里的GeometryObject:
发现这种类型是下面那些类型的父类;
所以我们可以这样写,先将这个方法返回的GeometryObject类型对象保存到一个变量中:
GeometryObject geomObj = e.GetGeometryObjectFromReference(myRef);
接下来我们要做的就是把geomObj转换成我们需要的边缘对象;我们知道Edge类型其实就是(is a)GeometryObject类型;
然而上面的帖子说了:
当需要转化对象的类型属于转换目标类型或者转换目标类型的派生类型时,那么此转换操作才能成功。
可是我们根据类图,发现这显然是矛盾的(代码把基类转换成了子类);
其实并不是依照这一点,as的转换还有一个重要的一点,上面的帖子没有说;那就是装箱和拆箱;这是因为这些类归根结底都继承自System.Object
类;
值类型和引用类型之间的转换是靠装箱和拆箱来实现的。
C#的类型系统是统一的,因此任何类型的值都可以按对象来处理。这意味着值类型可以“按需”转换为对象。由于这种统一性,使用Object类型的通用库(如.NET框架中的集合类)既可以用于引用类型,又可以用于值类型。
Object类:
System命名空间下有一个Object类,该类是所有类型的基类。C#语言中的类型都直接或间接地从Object类继承,因此,可以将Object类的对象显示转换为任意一种对象。
但是,值类型如何与Object类型之间转换呢?举个例子,在程序中可以直接这样写:
string s = (10).ToString;
数字10只是一个在堆栈上的4字节的值,怎么能实现调用上它的方法呢?实际上,C#语言是通过装箱操作来实现的,即先把10转换成Object类型,然后再调用Object类型的ToString方法来实现转换功能。
装箱:
装箱操作是将值类型隐式地转换为Object类型。装箱一个数值会为其分配一个对象实例,并把该数值复制到新对象中。例如:
int i = 123;
object o = i;
这条语句执行的结果是在堆栈中创建了一个对象o,该对象引用了堆上int类型的数值,而该数值是赋给变量i的数值的备份。
拆箱:
拆箱操作是指显示地把Object类型转换为值类型。拆箱操作包括以下两个步骤:
- 检查对象实例,确认它是否包装了值类型的数。
- 把实例中的值复制到值类型的变量中。
例子:
int i = 123;
object box = i;
int j = (int)box;
可以看出,拆箱正好是装箱的逆过程,但必须注意的是,装箱和拆箱必须遵循类型兼容的原则;
回到现场,代码哪里用到了装箱和拆箱呢?
这一步当中:
使用PickObject返回类型是引用类型Reference,其实这个myRef就是Edge对象的一个引用;然后下面的一步:
红色框实际就相当于拆解成两句话:
Edge edge1 = ... //edge1就是那个引用;具体实现我们可以忽略,红色框中可以看做用引用的一份拷贝来工作;
GeometryObject geomObj = edge1;//geomObj其实就是在edge的基础上创建的;
最后再:
Edge edge = geomObj as Edge;
没有问题了;
我们直接定义一个Edge类型的变量来储存,并使用转换(cast)的方式,其关键字就是 as
;
这是Edge的参考手册:
其中有属性ApproximateLength:
返回一个double;
同样,也可以选择表面,方法也和上面一样,只不过返回引用类型的PickObject方法括号中要改成PickObject(ObjectType.Face)
:
我试过,如果不改:
编译虽然可以通过,但是在Revit中使用插件的时候:
本来你要选择面,可是依然只让你选择边缘(PickObject括号内缘故):
就算选择了,会报错:
这是因为Edge无法转换为Face;而使用as转换不成功,系统也不会报错,会返回null;所以face没有办法创建,故显示“未将对象引用设置到对象的实例”。
打个断点看看face是否是null:
调试窗口设置“附加到进程”:
设置断点,并在Revit中运行该程序:
是的,确实是null,也就是说没有办法转换。
总结:
在Revit二次开发中,使用as并不能完全参照C#中as的基本规则了;除了继承关系要注意之外,还要注意引用类型,两者都考虑清楚之后,再考虑用as进行转换。
我有一种感觉,就是在Revit中,都会出现这样“基类转换成子类”的现象;我想可能是因为在创建每一个基类对象的时候,就已经用子类对象进行初始化了。而子类对象从哪里来呢?其实就是选择操作帮你搞定了。我们知道,Revit是参数化建模,每一个族或者其他的元素都包含着大量的信息;从文档中选择的时候,这些信息都将被用于初始化其父类的对象,因此我认为就是这样的原理,造成这种所谓的"基类转换成子类"的错觉。
如果有问题,请及时评论~希望共同探讨。