C#本地變數聲明趣味解析

來源:互聯網
上載者:User
我們先來看看展波舉的例子:
http://blog.joycode.com/zhanbos/archive/2004/10/26/36605.aspx
在這個例子裡面我們看到,編譯器會檢查scope問題,目的是防止錯誤使用本地變數。但是據我研究,這裡面有“Bug”(注意雙引號),那麼會有什麼有趣的“Bug”呢?我來給大家一個簡單的例子:

        public void Test()
        {
            {
                int a;
            }
            {
                int a;
            }
        }

在這個Test函數裡面有兩對打括弧,標明兩個互不相屬的子範圍。這裡大家也許看的非常不習慣,因為沒有人光禿禿的寫這麼兩對大括弧的。我跟大家說:沒關係,編譯器承認光禿禿的大括弧的,這個也是標準C裡面的規範之一,作用就是把大括弧裡面的所有東西認為是“一句話”,準確點講是邏輯語句,同時內部是一個範圍,約束範圍內的本地變數不會往外傳播。如果大家實在看不習慣了,可以自行加上諸如while(true)之類的首碼,就習慣了。
那麼這段代碼有什麼Bug呢?沒有,確實沒有Bug,編譯順利通過。當然,顯示了兩個Warning,說a沒有被用到,無傷大雅。我們首先來分析一下,編譯器怎麼給把這個給弄通過的呢?我們用Reflector來看一下(當然,因為沒有切實的代碼,所以只能夠看IL,而不能夠看C#):.method public hidebysig instance void Test() cil managed
{
      // Code Size: 2 byte(s)
      .maxstack 0
      .locals (
            int32 num1,
            int32 num2)
      L_0000: nop 
      L_0001: ret 
}

 

哦!原來編譯器把內部的變數改名字了!或者說編譯器把他們當作完全不同的兩個變數來對待。同時我們在這裡也可以看出來,實際上在IL裡面時不區分範圍的,只有本地變數著一個簡單的概念。無論你在哪個範圍,在什麼時候開始聲明,實際上都是在函數的一開始用一個.locals這樣的偽語句來聲明的。這麼做是簡單省事的辦法,因為如果在使用者原始碼實際聲明的地方才在棧上面開闢空間,那麼最後函數退出的時候就不知道該釋放多少棧空間了。當然這不是不可以解決的,但是那樣的話增加了不必要的複雜度。如果我來設計.NET Framework,我也會通過進階語言的編譯器來約束範圍問題,而不是擺到IL裡面去解決。(畢竟IL裡面沒有這樣的功能不影響我們寫程式)稍微引申一下,我們就知道,一個函數裡面有多少個本地變數,取決於整個函數內部聲明了多少本地變數,而與變數所在範圍無關。在IL這一層裡面暫時我們沒有看到這樣的最佳化工作,我們可以看看這樣的代碼最後被編譯器編譯成什麼了(用Release模式編譯):        public int Test()
        {
            int b;
            b = new Random().Next(5);
            if (b < 5)
            {
                int a = new Random().Next(5);
                Console.WriteLine(a);
                b = a;
            }
            else
            {
                int a = new Random().Next(10);
                Console.WriteLine(a);
                b = a;
            }
            return b;
        }

Reflector 反編譯結果:public int Test()
{
      int num1 = new Random().Next(5);
      if (num1 < 5)
      {
            int num2 = new Random().Next(5);
            Console.WriteLine(num2);
            return num2;
      }
      int num3 = new Random().Next(10);
      Console.WriteLine(num3);
      return num3;
}

大家可以看到num1是b,num2和num3則是分別的兩個a。事實上這兩個a互相之間是沒有任何衝突的,也就是說是完全可以重用的,編譯原理裡面也有一個變數重用的最佳化,但是這裡看不到有這樣的最佳化,我覺得比較吃驚。雖然說這也可以算是一種Bug(嚴格說來是也不是),但是我要說的“Bug”不是這個。

分析完上面這些基本知識,我就來勁了:        public void Test()
        {
            {
                int a;
            }
            {
                int a;
            }
            int a;
        }

看,編譯出來之後卻出現了錯誤:
error CS0136: A local variable named 'a' cannot be declared in this scope because it would give a different meaning to 'a', which is already used in a 'child' scope to denote something else
哦,原來這個跟聲明的順序還沒有關係,只要子範圍裡面有a了,那就不能夠再定義這個變數了。這個難道跟IL裡面所有變數都在函數開始部分聲明有關係?看起來好像是這麼一回事,但是實際上不是,因為C#的編譯器完全可以像前面那樣,把最後一個a當作另外一個變數。這到底是怎麼回事呢?我們需要作本次探索的最後一個實驗:        public void Test()
        {
            a = 2;
            {
                int a;
            }
            {
                int a;
            }
            int a;
        }

這下可好,除了剛才那個錯誤之外,還多出來另外一個:
error CS0103: The name 'a' does not exist in the class or namespace 'ConsoleApplication1.Class2'
也就是說,編譯器根本就沒有把後面那個a當作從函數一開始的地方定義來看待。但是這兩個錯誤合起來反而容易讓我們產生這樣的錯覺和悖論:
因為前面兩個a在範圍外面就應該消失其影響力,那就不應該跟後面的a產生衝突。但現在既然你說了,第三個a的定義根前面那兩個a的其中某一個定義相衝突了,那我就只能夠認為後面這個a實際上在前兩個a被定義出來之前就已經存在了,因為後面這個a處於外層範圍,它不會在內層範圍失去作用之前失效,這樣還能夠解釋得通。可是這麼解釋我只能夠認為外層的a應該在函數一開始的地方就生效了(老式的C編譯器有一段時間確實是這樣的),可是偏偏還來一個CS0103錯誤!解釋不通,有“Bug”!

最後我來修正這個我一開始提出的說法,其實並沒有Bug。得出有Bug的結論,那是從純粹的文法角度看這個問題的,我也覺得應該容許在第三個a的定義出現,頂多隻給出一個Warning。但是微軟卻給出了一個錯誤,我想這是從避免不必要的Bug的角度考慮,盡量保護開發人員避免不必要的煩惱。開發人員確實很有可能在定義了第三個a的時候忘記第一二個a已經失效了,同時也忘記了自己定義過第三個a,還以為自己用的是第一個或者第二個a裡面的資料。不過對於這種解釋,我還是有意見的:既然約束已經縮窄到這個地步了,那為什麼要允許第二個a的定義呢?如果開發人員會忘記自己定義過第三個a,有什麼理由認為不會把第二個a的定義給忘記了,以為自己在用第一個a呢?

本來上面所寫的那些統統都是垃圾代碼,我認為,在一個函數內部根本就不應該有相同的變數來迷惑自己。C#的編譯器在這些問題方面確實有相當嚴謹的考慮,不過我還是覺得有一些“悖論”存在,如果能夠更加嚴謹,我認為只會更好。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.