我们知道 C#的语法与C++非常相似实现从C++向C#的转变其困难不在于语言本身而在于熟悉NET的可管理环境和对NET框架的理解
尽管C#与C++在语法上的变化是很小的几乎不会对我们有什么影响但有些变化却足以使一些粗心的C++编程人员时刻铭记在心在本篇文章中我们将讨论C++编程人员最容易犯的十个错误
陷阱 没有明确的结束方法
几乎可以完全肯定地说对于大多数C++编程人员而言C#与C++最大的不同之处就在于碎片收集这也意味着编程人员再也无需担心内存洩露和确保删除所有没有用的指针但我们再也无法精确地控制杀死无用的对象这个过程事实上在C#中没有明确的destructor
如果使用非可管理性资源在不使用这些资源后必须明确地释放它对资源的隐性控制是由Finalize方法(也被称为finalizer)提供的当对象被销毁时它就会被碎片收集程序调用收回对象所占用的资源
finalizer应该只释放被销毁对象占用的非可管理性资源而不应牵涉到其他对象如果在程序中只使用了可管理性资源那就无需也不应当执行Finalize方法只有在非可管理性资源的处理中才会用到Finalize方法由于finalizer需要占用一定的资源因此应当只在需要它的方法中执行finalizer
直接调用一个对象的Finalize方法是绝对不允许的(除非是在子类的Finalize中调用基础类的Finalize)碎片收集程序会自动地调用Finalize
从语法上看C#中的destructor与C++非常相似但其实它们是完全不同的C#中的destructor只是定义Finalize方法的捷径因此下面的二段代码是有区别的
~MyClass()
{
// 需要完成的任务
}
MyClassFinalize()
{
// 需要完成的任务
baseFinalize();
}
错误Finalize和Dispose使用谁?
从上面的论述中我们已经很清楚显性地调用finalizer是不允许的它只能被碎片收集程序调用如果希望尽快地释放一些不再使用的数量有限的非可管理性资源(如文件句柄)则应该使用IDisposable界面这一界面有个Dispose方法它能够帮你完成这个任务Dispose是无需等待Finalize被调用而能够释放非可管理性资源的方法
如果已经使用了Dispose方法则应当阻止碎片收集程序再对相应的对象执行Finalize方法为此需要调用静态方法GCSuppressFinalize并将相应对象的指针传递给它作为参数Finalize方法就能调用Dispose方法了据此我们能够得到如下的代码
public void Dispose()
{
// 完成清理操作
// 通知GC不要再调用Finalize方法
GCSuppressFinalize(this);
}
public override void Finalize()
{
Dispose();
baseFinalize();
}
对于有些对象可能调用Close方法就更合适(例如对于文件对象调用Close就比Dispose更合适)可以通过创建一个private属性的Dispose方法和public属性的Close方法并让Close调用Dispose来实现对某些对象调用Close方法
由于不能确定一定会调用Dispose而且finalizer的执行也是不确定的(我们无法控制GC会在何时运行)C#提供了一个Using语句来保证Dispose方法会在尽可能早的时间被调用一般的方法是定义使用哪个对象然后用括号为这些对象指定一个活动的范围当遇到最内层的括号时Dispose方法就会被自动调用对该对象进行处理
using SystemDrawing;
class Tester
{
public static void Main()
{
using (Font theFont = new Font(Arial f))
{
//使用theFont对象
} // 编译器将调用Dispose处理theFont对象
Font anotherFont = new Font(Courierf);
using (anotherFont)
{
// 使用anotherFont对象
} // 编译器将调用Dispose处理anotherFont对象
}
}
在本例的第一部分中Font对象是在Using语句中创建的当Using语句结束时系统就会调用Dispose对Font对象进行处理在本例的第二部分Font对象是在Using语句外部创建的在决定使用它时再将它放在Using语句内当Using语句结束时系统就会调用Dispose
Using语句还能防止其他意外的发生保证系统一定会调用Dispose
错误C#中的值型变量和引用型变量是有区别的
与C++一样C#也是一种强类型编程语言C#中的数据类型被分为了二大类C#语言本身所固有的数据类型和用户自定义数据类型这一点也与C++相似
此外C#语言还把变量分为值类型和引用类型除非是被包含在一个引用类型中值类型变量的值保留在栈中这一点与C++中的变量非常相似引用类型的变量也是栈的一种它的值是堆中对象的地址与C++中的指针非常地相似值类型变量的值被直接传递给方法引用型变量在被作为参数传递给方法时传递的是索引
类和界面可以创建引用类变量但需要指出的是结构数据类型是C#的一种内置数据类型同时也是一种值型的数据类型
错误注意隐性的数据类型转换
Boxing和unboxing是使值型数据类型被当作索引型数据类型使用的二个过程值型变量可以被包装进一个对象中然后再被解包回值型变量包括内置数据类型在内的所有C#中的数据类型都可以被隐性地转化为一个对象包装一个值型变量就会生成一个对象的实例然后将变量拷贝到实例中
Boxing是隐性的如果在需要索引型数据类型的地方使用了值型数据类型的变量值型变量就会隐性地转化为索引型数据类型的变量Boxing会影响代码执行的性能因此应当尽量避免尤其是在数据量较大的时候
如果要将一个打包的对象转换回原来的值型变量必须显性地对它进行解包解包需要二个步骤首先对对象实例进行检查确保它们是由值型的变量被包装成的第二步将实例中的值拷贝到值型变量中为了确保解包成功被解包的对象必须是通过打包一个值型变量的值生成的对象的索引
using System;
public class UnboxingTest
{
public static void Main()
{
int i = ;
//打包
object o = i;
// 解包(必须是显性的)
int j = (int) o;
ConsoleWriteLine(j: {} j);
}
}
如果被解包的对象是无效的或是一个不同数据类型对象的索引就会产生InvalidCastException异外
错误结构与对象是有区别的
C++中的结构与类差不多唯一的区别是在缺省状态下结构的访问权限是public其继承权限也是public一些C++编程人员将结构作为数据对象但这只是一个约定而非是必须这样的
在C#中结构只是一个用户自定义的数据类型并不能取代类尽管结构也支持属性方法域和操作符但不支持继承和destructor
更重要的是类是一种索引型数据类型结构是值型数据类型因此结构在表达无需索引操作的对象方面更有用结构在数组操作方面的效率更高而在集合的操作方面则效率较低集合需要索引结构必须打包才适合在集合的操作中使用类在较大规模的集合操作中的效率更高