這是我們某個組員在編程過程中提出的疑問。因為這個編譯錯誤很容易避免,所以我一直也沒有仔細想過這個問題,直到看過他的代碼后才意識到,此問題并不是那么簡單的。
先看看這段代碼:
代碼
class PRogram
{
static void Main(string[] args)
{
byte[] buf = new byte[1024];
T t = new T();
string str = "1234";
int n = 1234;
int? nn = 1234;
DateTime dt = DateTime.Now;
object o = 1234;
Console.WriteLine("finish");
}
}
class T { }
你覺得這段代碼里有幾個變量沒有使用過呢?
如果從程序員的角度來看,答案應該是所有變量都沒有使用過。但編譯器給出的結果卻有點違反直覺:
變量“str”已賦值,但其值從未使用過
變量“n”已賦值,但其值從未使用過
變量“nn”已賦值,但其值從未使用過
奇怪的地方在于,雖然所有變量都是用同樣的方式聲明,但編譯器卻只認為其中一部分沒有使用過。這是怎么回事呢?
我們一個一個來分析。首先看看數組,如果使用默認值的話,編譯器給出的信息就不同了:
byte[] buf1 = null; // 有警告
byte[] buf2 = new byte[1024]; // 沒有警告
這個結果似乎表明,如果參數賦值為null,那么編譯器并不會真的執行賦值,并且變量會當作沒有使用過。用IL檢查的結果也可以證明此說法:對第一行,編譯器沒有生成任何對應的語句;對第二條則使用了newattr指令來創建數組。
對于自定義的類:
T t1 = null; // 有警告
T t2 = new T(); // 沒有警告
這個結果應當是可以理解的(盡管可以理解,但我認為并不好,理由見后)。雖然我們并沒有調用該類的任何方法,但是類的構造函數仍然可能執行某些操作,所以只要創建了一個類,編譯器就會把它當作已經使用過的。
對于基本值類型,其表現和引用類型又有所不同,編譯器并不把初始賦值當作對變量的使用:
int n1 = 0; // 有警告
int n2 = 1234; // 有警告
int? n3 = null; // 有警告
int? n4 = 0; // 有警告
int? n5 = 1234; // 有警告
string從實現上來說應當算是引用類型,但表現上卻更加類似于值類型,警告信息也和值類型相同。
對于稍微復雜一些的值類型,結果有點微妙:
DateTime dt1; // 有警告
DateTime dt2 = new DateTime(); // 有警告
DateTime dt3 = new DateTime(2009,1,1); // 沒有警告
DateTime dt4 = DateTime.Now; // 沒有警告
這個結果有一點是需要注意的。盡管DateTime的默認構造函數和帶參構造函數從用戶角度看同樣是構造函數,但在編譯器的角度來看卻是不一樣的。用IL反編譯也可以看出,如果調用默認構造函數的話,那么編譯器調用的是initobj指令,而對帶參構造函數調用的則是call ctor指令。此外,盡管從程序員的角度來看賦值代碼的格式是完全相同的,但編譯器卻會根據所賦的值不同而采取不同的構造策略,這也是比較違反直覺的。
最后的結論比較遺憾,那就是C#的編譯警告并不足以給予程序員足夠的保護,特別是對于數組:
byte[] buf = new byte[1024];
如果僅構造這樣一個數組而沒有使用的話,那么編譯器并不會給予程序員任何警告信息。
另外一個問題也是值得考慮的,聲明一個類而不使用任何方法,比如僅僅
T t = new T()
這是合理的行為嗎?編譯器應該為此發出警告嗎?
我個人的看法是,從使用的角度來說,這是不合理的,應當盡量避免,編譯器發現此用法的話應該提出警告。如果確實有需要的話,可以通過編譯指令或Attribute的方法來特別聲明來避免警告信息。然而C#編譯器的行為卻是不發出警告,這一點我是不認同的。當然,我也希望大家提出自己的想法。
新聞熱點
疑難解答