Top Ten Traps in C# for C++ Programmers中文版(下篇) (轉)

worldblog發表於2007-12-11
Top Ten Traps in C# for C++ Programmers中文版(下篇) (轉)[@more@]

Top Ten Traps in for C++ Programmers中文版:namespace prefix = o ns = "urn:schemas--com::office" />

作者:Jesse Liberty

譯者:榮耀

【譯序:C#文章。請注意:所有環境為Microsoft 7.0 Beta2和 Microsoft SDK Beta2。限於譯者時間和能力,文中倘有訛誤,當以英文原版為準】

陷阱六.虛方法必須被顯式過載

  在C#中,如果程式設計師決定過載一個虛方法,他(她)必須顯式使用overr關鍵字。

  讓我們考察一下這樣做的好處。假定公司A寫了一個Window類,公司B購買了公司A的Window類的一個複製作為基類。公司B的程式設計師從中派生【譯註:原文為...using...,從下文來看,顯然是“派生”之意。事實上,使用類的方式還有“組合”(也有說為“嵌入”或“包容”(COM語義)等等),後者不存在下文所描述的問題】出ListBox和RadioButton類。公司B的程式設計師不知道或不能控制Window類的設計,包括公司A將來對Window類可能做的修改。

  現在假定公司B的程式設計師決定為ListBox加入一個Sort方法:

public class ListBox : Window

{

  public virtual void Sort() {"}

}

這是沒有問題的—直到公司A的Window類作者釋出了Window類的版本2,公司A的程式設計師向Window類也加入了一個public的Sort方法:

public class Window

{

  // "

  public virtual void Sort() {"}

}

在C++中,Window類新的虛方法Sort將會作為ListBox虛方法的基類方法。當你試圖Window的Sort時,實際上呼叫的是ListBox的Sort。C#中虛方法【譯註:原文寫成virtual function】永遠被認為是虛擬排程的根。這就是說,只要C#找到了一個虛方法,它就不會再沿著繼承層次進一步尋找了,如果一個新的Sort虛方法被引入Window,ListBox的執行時行為不會被改變。當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關鍵字即可。

陷阱七:不可以在頭部進行初始化

  C#裡的初始化不同於C++。假定你有一個類Person,它有一個私有成員變數age;一個派生類Employee,它有一個私有成員變數salaryLeverl。在C++中,你可以在Employee構造器的成員初始化列表部分初始化salaryLevel:

Employee::Employee(int theAge, int theSalaryLevel):

Person(theAge)  // 初始化基類

salaryLevel(theSalaryLevel)  // 初始化成員變數

{

  // 構造器體

}

在C#中,這個構造器是的。儘管你仍可以如此初始化基類,但對成員變數的初始化將導致一個編譯時錯誤。當然,你可以在成員變數宣告處對其賦初始值:

Class Employee : public Person

{

  // 在這兒宣告

private salaryLevel = 3;  //初始化

}

【譯註:以上程式碼有誤LC#中,正確寫法如下:

  class Employee: Person

{

  private int salaryLevel = 3;

}

注意:你不需要在每一個類宣告的後面都加上一個分號,每一個成員都必須要有顯式的訪問級別宣告。

陷阱8.不能把布林值轉換為整型值

  在C#中,布林值(true、false)不同於整型值。因此,不能這麼寫:

if  ( someFuncWhichReturnsAValue() )//【譯註:假定這個方法不返回布林值】

也不能指望如果someFuncWhichReturnsAValue返回一個0它將等於false,否則為true。一個好訊息是誤用賦值運算子而不是相等運算子的老毛病不會再犯了。因此,如果這麼寫:

if ( x = 5 )

將會得到一個編譯時錯誤,因為x = 5的結果為5,而它不是布林值。

【譯註:以下是C++裡一不小心會出的邏輯錯誤,編譯器不會有任何提示L執行得很順暢,不過結果不是你想要的:

C++:

#include "stdafx.h"

int main(int argc, char* argv[])

{

  int n = 0;

  if (n = 1)//編譯器啥都沒說L一般推薦寫為1 == n,萬一寫成1 = n編譯器都不同意J

  {

    printf("1n");

  }

  else

  {

    printf("0n");

  }

  return 0;

}

以上執行結果為1,這未必是你想要的。

C#:

using System;

public class RyTestBoolApp

{

  public static void Main()

  {

  int n = 0;

  if (n = 1)//編譯器不同意J無法將int轉換成bool

  {

    Console.WriteLine("1");

  }

  else

  {

    Console.WriteLine("0");

  }

  }

}

但是,如果是這種情況:

bool b = false;

if (b = true)

...

不管是C++還是C#都沒招L

【譯註:C++程式設計師一般是喜歡這種自由的寫法:

  if (MyRef)

  if (MyInt)

但在C#裡,必須寫成:

  if (MyRef == null)//或if (null == MyRef)

  if (MyInt == 0)//或if (0 == MyInt)

等。

陷阱九.switch語句不會“貫穿”

在C#中,case語句不會貫穿到下一句—如果在該case裡有程式碼的話。因此,儘管下面的程式碼在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();

}

【譯註:以下是使用switch的完整例子,它還說明了switch語句支配的型別可以是字串,並演示了屬性的使用方法。

using System;

class RySwitchTest

{

  public RySwitchTest(string AStr)

  {

    this.StrProperty = AStr;

  }

  protected string StrField;

  public string StrProperty

  {

  get

  {

    return this.StrField;

  }

  set

  {

    this.StrField = value;

  }

  }

  public void SwitchStrProperty()

  {

  switch (this.StrProperty)

  {

    case ("ry01"):

    Console.WriteLine("ry01");

    break;

    case ("ry02"):

    Console.WriteLine("ry02"); 

    break;//如果這一行註釋掉,編譯器會報控制不能從一個case標籤(case "ry02":)貫穿到另一個標籤,如果你確實需要,可以這麼寫:goto case ("ry03");或goto default。

    case ("ry03"):

    Console.WriteLine("ry03");

    break;

    default:

    Console.WriteLine("default");

    break;

  }

  }

}

class RySwitchTestApp

{

  public static void Main()

  {

    RySwitchTest rst = new RySwitchTest("ry02");

    rst.SwitchStrProperty();

  }

}

陷阱十.C#需要明確的賦值操作

  C#要求必須進行明確地賦值操作,這就意味所有的變數在被使用前必須被賦值。因此,儘管你可以宣告未初始化的變數,但在它擁有值之前是不可以被傳遞到方法的。

  這就引出了一個問題—若你僅僅想把變數按引用傳遞給方法,就象一個“出”引數。例如,假定你有個方法,返回當前的小時、分鐘和秒。如果這麼寫:

int theHour;

int theMinute;

int theSecond;

time.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引數修飾符。out修飾符避免了對引用引數也需要初始化的需求。例如,為GetTime提供的引數沒有提供給方法任何資訊,它們僅僅是要從方法裡取得資訊的機制。因此,把這三個都標記為out引數,就避免了在方法外初始化它們的需要。當從被傳入的方法返回時,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);

【譯註:完整示例如下:

C#:[例1:使用ref修飾的方法引數]

using System;

class RyRefTest

{

  public RyRefTest()

  {

    this.IntField = 1;

    this.StrField = "StrField";

  }

  protected int IntField;

  protected string StrField;

  public void GetFields(ref int AInt, ref string AStr)

  {

  AInt = this.IntField;

  AStr = this.StrField;

  }

}

class RyRefTestApp

{

  public static void Main()

  {

    RyRefTest rrt = new RyRefTest();

  int IntVar = 0;//如果是int IntVar; 編譯器會報使用了未賦值的變數IntVar

  string StrVar = "0";//如果是string StrVar; 編譯器會報使用了未賦值的變數StrVar

    rrt.GetFields(ref IntVar, ref StrVar);

    Console.WriteLine("IntVar = {0}, StrVar = {1}", IntVar, StrVar);

  }

}

 

C#:[例2:使用out修飾的方法引數]

using System;

class RyRefTest

{

  public RyRefTest()

  {

    this.IntField = 1;

    this.StrField = "StrField";

  }

  protected int IntField;

  protected string StrField;

  public void GetFields(out int AInt, out string AStr)

  {

  AInt = this.IntField;

  AStr = this.StrField;

  }

}

class RyRefTestApp

{

  public static void Main()

  {

    RyRefTest rrt = new RyRefTest();

  int IntVar;//這樣就可以了,如果寫成int IntVar = 0;當然也沒問題J

  string StrVar; //這樣就可以了,如果寫成string StrVar = "0";當然也沒問題J

    rrt.GetFields(out IntVar, out StrVar);

    Console.WriteLine("IntVar = {0}, StrVar = {1}", IntVar, StrVar);

  }

}

【譯註:如欲瞭解更多,請參閱A Comparative Overview of C#中文版(上篇)A Comparative Overview of C#中文版(下篇)ASP?id=11580">C#首席設計師Anders Hejlsberg專訪

-全文完-


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-991341/,如需轉載,請註明出處,否則將追究法律責任。

相關文章