C++编程人员容易犯的10个C#错误
副标题#e#
我们知道,C#的语法与C++很是相似,实现从C++向C#的转变,其坚苦不在于语言自己,而在于熟悉.NET的可打点情况和对.NET框架的领略。尽量C#与C++在语法上的变革是很小的,险些不会对我们有什么影响,但有些变革却足以使一些粗心的C++编程人员时刻铭刻在心。在本篇文章中我们将接头C++编程人员最容易犯的十个错误。
陷阱1: 没有明晰的竣事要领
险些可以完全必定地说,对付大大都C++编程人员而言,C#与C++最大的差异之处就在于碎片收集。这也意味着编程人员再也无需担忧内存泄露和确保删除所有没有用的指针。但我们再也无法准确地节制杀死无用的工具这个进程。事实上,在C#中没有明晰的destructor。
假如利用非可打点性资源,在不利用这些资源后,必需明晰地释放它。对资源的隐性节制是由Finalize要领(也被称为finalizer)提供的,当工具被销毁时,它就会被碎片收集措施挪用收回工具所占用的资源。finalizer应该只释放被销毁工具占用的非可打点性资源,而不该牵涉到其他工具。假如在措施中只利用了可打点性资源,那就无需也不应当执行Finalize要领,只有在非可打点性资源的处理惩罚中才会用到Finalize要领。由于finalizer需要占用必然的资源,因此该当只在需要它的要领中执行finalizer。直接挪用一个工具的Finalize要领是绝对不答允的(除非是在子类的Finalize中挪用基本类的Finalize。),碎片收集措施会自动地挪用Finalize。
从语法上看,C#中的destructor与C++很是相似,但其实它们是完全差异的。C#中的destructor只是界说Finalize要领的捷径。因此,下面的二段代码是有区此外:
~MyClass()
{ // 需要完成的任务
}
MyClass.Finalize() {// 需要完成的任务
base.Finalize();
}
错误2:Finalize和Dispose利用谁?
从上面的阐述中我们已经很清楚,显性地挪用finalizer是不答允的,它只能被碎片收集措施挪用。假如但愿尽快地释放一些不再利用的数量有限的非可打点性资源(如文件句柄),则应该利用IDisposable界面,这一界面有个Dispose要领,它可以或许帮你完成这个任务。Dispose是无需期待Finalize被挪用而可以或许释放非可打点性资源的要领。
假如已经利用了Dispose要领,则该当阻止碎片收集措施再对相应的工具执行Finalize要领。为此,需要挪用静态要领GC.SuppressFinalize,并将相应工具的指针通报给它作为参数,Finalize要领就能挪用Dispose要领了。据此,我们可以或许获得如下的代码:
public void Dispose()
{
// 完成清理操纵
// 通知GC不要再挪用Finalize要领
GC.SuppressFinalize(this);
}
public override void Finalize() {
Dispose(); base.Finalize();
}
对付有些工具,大概挪用Close要领就更符合(譬喻,对付文件工具挪用Close就比Dispose更符合),可以通过建设一个private属性的Dispose要领和public属性的Close要领,并让Close挪用Dispose来实现对某些工具挪用Close要领。
由于不能确定必然会挪用Dispose,并且finalizer的执行也是不确定的(我们无法节制GC会在何时运行),C#提供了一个Using语句来担保Dispose要了解在尽大概早的时间被挪用。一般的要领是界说利用哪个工具,然后用括号为这些工具指定一个勾当的范畴,当碰着最内层的括号时,Dispose要领就会被自动挪用,对该工具举办处理惩罚。
using System.Drawing;
class Tester
{
public static void Main()
{
using (Font theFont = new Font("Arial", 10.0f))
{
//利用theFont工具
} // 编译器将挪用Dispose处理惩罚theFont工具
Font anotherFont = new Font("Courier",12.0f);
using (anotherFont)
{
// 利用anotherFont工具
} // 编译器将挪用Dispose处理惩罚anotherFont工具 }
}
#p#副标题#e#
在本例的第一部门中,Font工具是在Using语句中建设的。当Using语句竣事时,系统就会挪用Dispose,对Font工具举办处理惩罚。在本例的第二部门,Font工具是在Using语句外部建设的,在抉择利用它时,再将它放在Using语句内,当Using语句竣事时,系统就会挪用Dispose。Using语句还能防备其他意外的产生,担保系统必然会挪用Dispose。
错误3:C#中的值型变量和引用型变量是有区此外
与C++一样,C#也是一种强范例编程语言。C#中的数据范例被分为了二大类:C#语言自己所固有的数据范例和用户自界说数据范例,这一点也与C++相似。
另外,C#语言还把变量分为值范例和引用范例。除非是被包括在一个引用范例中,值范例变量的值保存在栈中,这一点与C++中的变量很是相似。引用范例的变量也是栈的一种,它的值是堆中工具的地点,与C++中的指针很是地相似。值范例变量的值被直接通报给要领,引用型变量在被作为参数通报给要领时,通报的是索引。类和界面可以建设引用类变量,但需要指出的是,布局数据范例是C#的一种内置数据范例,同时也是一种值型的数据范例。
错误4:留意隐性的数据范例转换
#p#分页标题#e#
Boxing和unboxing是使值型数据范例被看成索引型数据范例利用的二个进程。值型变量可以被包装进一个工具中,然后再被解包回值型变量。包罗内置数据范例在内的所有C#中的数据范例都可以被隐性地转化为一个工具。包装一个值型变量就会生成一个工具的实例,然后将变量拷贝到实例中。
Boxing是隐性的,假如在需要索引型数据范例的处所利用了值型数据范例的变量,值型变量就会隐性地转化为索引型数据范例的变量。Boxing会影响代码执行的机能,因此该当只管制止,尤其是在数据量较大的时候。
假如要将一个打包的工具转换回本来的值型变量,必需显性地对它举办解包。解包需要二个步调:首先对工具实例举办查抄,确保它们是由值型的变量被包装成的;第二步将实例中的值拷贝到值型变量中。为了确保解包乐成,被解包的工具必需是通过打包一个值型变量的值生成的工具的索引。
using System;
public class UnboxingTest
{
public static void Main()
{
int i = 123; //打包
object o = i; // 解包(必需是显性的)
int j = (int) o;
Console.WriteLine("j: {0}", j); }
}
假如被解包的工具是无效的,或是一个差异数据范例工具的索引,就会发生InvalidCastException异外。
错误5:布局与工具是有区此外
C++中的布局与类差不多,独一的区别是,在缺省状态下,布局的会见权限是public,其担任权限也是public。一些C++编程人员将布局作为数据工具,但这只是一个约定而非是必需这样的。在C#中,布局只是一个用户自界说的数据范例,并不能代替类。尽量布局也支持属性、要领、域和操纵符,但不支持担任和destructor。
更重要的是,类是一种索引型数据范例,布局是值型数据范例。因此,布局在表达无需索引操纵的工具方面更有用。布局在数组操纵方面的效率更高,而在荟萃的操纵方面则效率较低。荟萃需要索引,布局必需打包才适合在荟萃的操纵中利用,类在较大局限的荟萃操纵中的效率更高。
错误6:虚要领必需被明晰地包围
在C#语言中,编程人员在包围一个虚要领时必需显性地利用override关健字。假设一个Window类是由A公司编写的,ListBox和RadioButton类是由B公司的和编程人员在购置的A公司编写的Window类的基本上编写的,B公司的编程人员对包罗Window类将来的变革环境在内的设计知之甚少。假如B公司的一位编程人员要在ListBox上添加一个Sort要领:
public class ListBox : Window
{ public virtual void Sort() {"}
}
在A公司宣布新版的Window类之前,这不会有任何问题。假如A公司的编程人员也在Window类中添加了一个Sort要领。
public class Window
{ // " public virtual void Sort() {"}
}
在C++中,Windows类中的Sort要领将成为ListBox类中Sort要领的基本要领,在但愿挪用Windows类中的Sort要领时,ListBox类中的Sort要领就会被挪用。在C#中,虚拟函数老是被认为是虚拟调治的根。也就是说,一旦C#发明一个虚拟的要领,就不会再在虚拟链中查找其他虚拟要领。假如ListBox再次被编译,编译器就会生成一个告诫信息:
"\class1.cs(54,24): warning CS0114: 'ListBox.Sort()' hides
inherited member 'Window.Sort()'.
要使当前的成员包围本来的要领,就需要添加override关健字,可能添加new关健字。
要消除告诫信息,编程人员必需搞清楚他想干什么。可以在ListBox类中的Sort要领前添加new,表白它不该该包围Window中的虚要领:
public class ListBox : Window {
public new virtual void Sort() {"}
这样就可以排除告诫信息。假如编程人员确实但愿包围掉Window中的要领,就必需利用override关健字来显性地表白其意图。
错误7:类成员变量的初始化
C#中的初始化与C++中差异。假设有一个带有private性质的成员变量age的Person类,Employee是由担任Person类而生成的,它有一个private性质的salaryLevel成员变量。在C++中,我们可以在Employee的结构器的初始化部门初始化salaryLevel,如下面的代码所示:
#p#分页标题#e#
Employee::Employee(int theAge, int theSalaryLevel):
Person(theAge) // 初始化基本类
salaryLevel(theSalaryLevel) // 初始化成员变量
{
// 结构器的代码
}
这种要领在C#中是犯科的。尽量仍然可以初始化基本类,但象上面的代码那样对成员变量初始化就会引起编译错误。在C#中,我们可以在界说成员变量时的同时对它举办初始化:
Class Employee : public Person
{ // 成员变量的界说
private salaryLevel = 3; // 初始化
}
留意:必需明晰地界说每个变量的会见权限。
错误8:布尔型变量与整型变量是两回事儿
if( someFuncWhichReturnsAValue() )
在C#中,布尔型变量与整型变量并不沟通,因此下面的代码是不正确的:
if( someFuncWhichReturnsAValue() )
if someFuncWhichReturnsAValue返回零暗示false,不然暗示true的想法已经行不通了。这样的长处是本来存在的将赋值运算与相等相夹杂的错误就不会再犯了。因此下面的代码:
if ( x = 5 )
在编译时就会堕落,因为x=5只是把5赋给了X,而不是一个布尔值。
错误9:switch语句中会有些语句执行不到
在C#中,假如一个switch语句执行了一些操纵,则措施就大概不能执行到下一个语句。因此,尽量下面的代码在C++中是正当的,但在C#中却不正当:
switch (i)
{
case 4:
CallFuncOne(); case 5: // 错误,不会执行到这里
CallSomeFunc();
}
要实现上面代码的目标,需要利用一个goto语句:
switch (i)
{
case 4: CallFuncOne();
goto case 5; case 5:
CallSomeFunc();
}
假如case语句不执行任何代码,则所有的语句城市被执行。如下面的代码:
switch (i)
{
case 4: // 能执行到 case 5: // 能执行到
case 6: CallSomeFunc();
}
错误10:C#中的变量要求明晰地赋值
在C#中,所有的变量在利用前都必需被赋值。因此,可以在界说变量时差池它举办初始化,假如在把它通报给一个要领前,必需被赋值。
假如只是通过索引向要领通报一个变量,而且该变量是要领的输出变量,这是就会带来问题。譬喻,假设有一个要领,它返回当前时间的小时、分、秒,假如象下面这样编写代码:
int theHour;
int theMinute;
int theSecond;
timeObject.GetTime( ref theHour, ref theMinute, ref theSecond)
假如在利用theHour、theMinute和theSecond这三个变量之前没有对它们举办初始化,就会发生一个编译错误:
Use of unassigned local variable 'theHour'
Use of unassigned local variable 'theMinute'
Use of unassigned local variable 'theSecond'
我们可以通过将这些变量初始化为0或其他对要领的返回值没有影响的值,以办理编译器的这个小问题:
int theHour = 0;
int theMinute = 0;
int theSecond = 0;
timeObject.GetTime( ref theHour, ref theMinute, ref theSecond)
这样就有些太贫苦了,这些变量通报给GetTime要领,然后被改变罢了。为了办理这一问题,C#专门针对这一环境提供了out参数修饰符,它可以使一个参数无需初始化就可以被引用。譬喻,GetTime中的参数对它自己没有一点意义,它们只是为了表达该要领的输出。在要领中返回之前,Out参数中必需被指定一个值。下面是颠末修改后的GetTime要领:
public void GetTime(out int h, out int m, out int s) {
h = Hour;
m = Minute;
s = Second;
}
下面是新的GetTime要领的挪用要领:
timeObject.GetTime( out theHour, out theMinute, out theSecond);