1. 程式人生 > >C#中字段、屬性、只讀、構造函數賦值、反射賦值的相關

C#中字段、屬性、只讀、構造函數賦值、反射賦值的相關

失敗 inter 註意 ring 空間 反射 存儲 進行 bind

C#中字段、屬性和構造函數賦值的問題

提出問題

首先提出幾個問題:

1、如何實現自己的註入框架?

2、字段和自動屬性的區別是什麽?

3、字段和自動屬性聲明時的直接賦值和構造函數賦值有什麽區別?

4、為什麽只讀字段和只讀自動屬性(只有get沒有set訪問器)都可以在構造函數中進行賦值?

5、反射可以給只讀字段或者只讀屬性進行賦值嗎?

6、自動屬性和普通屬性的區別?

這些問題是我在試著寫自己的註入實現時遇到的問題。這些問題應該在學習C#時的第一節課就應該學到了,我看網上還有人分享說他在面試時遇到面試官問為什麽只讀字段和只讀自動屬性可以在構造函數中進行賦值,他沒有回答上來,然後他寫文章探討這個問題,卻沒有得出一個明顯的答案,實在可惜。網上關於只讀屬性有些是寫ReadOnly特性的,讀到這些文章直接跳過吧

,老版本的C#現在看也沒什麽幫助。

給出答案

2、屬性比字段多了get/set訪問器;字段是在內存中聲明的一個內存空間,可以實實在在的存儲值;屬性像字段一樣使用,卻可以有自己的代碼段,能賦值取值,是因為訪問屬性就是調用屬性的get/set方法對字段進行取值賦值(或者不操作字段);在MSDN上,建議字段作為類的私有變量使用private/protected修飾,屬性則往往作為共有屬性使用public修飾;字段的讀取和操作都是直接操作內存,屬性是調用get/set訪問器,所以字段比屬性快

3、準確來說,沒有區別。區別僅僅是直接賦值先執行,構造函數賦值後執行。在生成的IL中間語言(C#代碼先編譯成IL代碼,然後才編譯成匯編語言)中,字段直接賦值和構造函數賦值是在同一個代碼段中

(構造函數中)的。

4、這個問題可以和上面的問題聯合起來回答。構造函數作為實例化一個類的入口,是最先訪問的。字段的直接賦值其實也是放在構造函數中執行的,所以才說直接賦值和構造函數賦值沒有區別。“只讀”的限制只是由C#編譯器(CLR)維護的,我覺得全名應該叫做“除構造函數外只讀”更加準確,這是C#語法的規則記住就行(這是當然,直接賦值其實是放在構造函數中進行賦值的,如果構造函數不能賦值那只讀字段沒有值和沒有聲明一樣);

5、這個問題又可以和上面的問題聯系起來一起回答。通過反射可以給自讀字段賦值但是無法給只讀屬性進行賦值(不相信的可以試一下)。對只讀字段的賦值是因為繞過了C#編譯器(CLR)的只讀顯示,對只讀屬性賦值的話是還是調用set訪問器對字段進行賦值,因為沒有set訪問器所以允許後會報錯。那麽問題來了,那為什麽只讀自動屬性沒有set訪問器還可以在構造函數中賦值呢?其實只讀自動屬性在構造函數中進行賦值,實質上是對字段進行賦值

,和屬性的get/set訪問器沒有關系。

6、區別是什麽?上面一直強調自動屬性,是因為自動屬性和普通屬性不一樣,比如只讀普通屬性(沒有set訪問器)無法在構造函數中賦值。在沒有自動屬性之前,普通屬性使用步驟是首先聲明一個字段如_id,然後聲明一個屬性Id,在get和set訪問器中做一些操作,這些操作大多數是對字段_id的操作,但是有時候和字段沒有關系。普通屬性可以像字段一樣通過“.”的方式調用,但又像方法一樣具有代碼段(普通屬性從來不開辟內存空間)。

但是C#3.0之後引入了自動屬性,聲明方式如public int id { get; set; },C#6.0之後又有了public string FirstName { get; set; } = "Jane"。自動屬性肯定開辟了內存空間然後才有了自動屬性的直接賦值。其實在類中聲明自動屬性會在編譯成IL中間語言中聲明一個隱藏字段,然後生成隱藏字段的get/set方法,然後生成get/set訪問器。這裏可以解釋為什麽只讀普通屬性無法在構造函數中賦值(和直接賦值)而只讀自動屬性可以在構造函數中賦值(和直接賦值),因為不論直接賦值還是在構造函數中賦值,生成的IL代碼中的構造函數中,操作的都是隱藏字段,並沒有訪問屬性的set訪問器。(註意這裏只是說的類中的自動屬性,接口中也是可以有自動屬性的,但是接口的自動屬性並不會生成隱藏字段只是定義get/set訪問器)

開始解釋

通過C#生成的IL中間語言代碼可以知道的更清楚

    public class User
    {
        public int id = 0;
        public int age { get; set; } = 1;
        public User()
        {
            id = 2;
            age = 3;
        }
    }

技術分享圖片技術分享圖片

可以看到,自動屬性會生成一個名稱為 ‘<age>k__BackingField‘ 的隱藏私有字段+私有字段的get/set方法+屬性代碼段;

可以看到IL代碼生成了User的構造函數 .ctor,ctor是構造函數(Constructor)。

不論直接賦值還是構造函數賦值,都是在.ctor中執行的,並且操作的都是字段,自動屬性的賦值操作的是隱藏字段

  public interface IUser
  {
    int id { get; set; }
  }

技術分享圖片

可以看到,接口中的自動屬性並沒有生成隱藏字段

其他說明

1、上文中提到“反射可以給只讀字段進行賦值但是無法給只讀屬性進行賦值”。無法給只讀屬性進行賦值是因為沒有set訪問器。但是我們已經知道了可以給字段賦值,並且只讀屬性會生成隱藏字段,那我們是不是可以通過給隱藏字段進行賦值間接達到給自動屬性賦值的目的呢?答案是可以的!

定義User的只讀自動屬性

    public class User
    {
        public int age { get;  } = 1;
        public User()
        {
            age = 3;
        }
    }

控制臺的反射賦值代碼:

            var user = new User();
            try { typeof(User).GetProperty("age").SetValue(user, 9); }
            catch{    Console.WriteLine("只讀屬性賦值失敗");}
            typeof(User).GetField("<age>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(user,9);
            Console.WriteLine(user.age);
            Console.Read();

運行

技術分享圖片

2、因為隱藏字段是私有的,所以取到隱藏字段需要 BindingFlags.NonPublic

3、只讀自動屬性說明不想被訪問到那為什麽還要給它賦值呢?這個問題……做著玩,項目中我覺得也沒有什麽用到的機會……

C#中字段、屬性、只讀、構造函數賦值、反射賦值的相關