歡迎光臨
每天分享高質量文章

C#中欄位、屬性、只讀、建構式賦值、反射賦值的相關

來自:GetPower

連結:http://www.cnblogs.com/gdpw/p/9463145.html

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;

    }

}

可以看到,自動屬性會生成一個名稱為 ‘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(“k__BackingField”, BindingFlags.Instance | BindingFlags.NonPublic).SetValue(user,9);

Console.WriteLine(user.age);

Console.Read();

執行

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

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



●編號138,輸入編號直達本文

●輸入m獲取文章目錄

推薦↓↓↓

Web開發

更多推薦18個技術類公眾微信

涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

贊(0)

分享創造快樂