IL角度理解C#中欄位,屬性與方法的區別
目錄- IL角度理解C#中欄位,屬性與方法的區別
- 1.欄位,屬性與方法的區別
- 2. 欄位,屬性與方法的IL代碼
- 2.1 C#代碼
- 2.2 IL代碼分析
- 2.2.1 欄位的IL代碼
- 2.2.2 屬性的IL代碼
- 2.2.2.1 屬性
- 2.2.2.2 自動生成屬性
- 2.2.3 方法的IL代碼分析
- 3 屬性的功能
- 3.1 設定只讀屬性
- 3.2 呼叫方法
- 3.3 賴加載
- 3.4 介面繼承
- 3.5 屬性做個簡單的校驗
- 3.6 屬性中呼叫事件
- 4 欄位的優越性
- 4.1 屬性賦值代碼
- 4.2 欄位賦值
- 5 小技巧
- 6 ref參考的本質
1.欄位,屬性與方法的區別
欄位的本質是變數,直接在類或者結構體中宣告,類或者結構體中會有實體欄位,靜態欄位等(靜態欄位可實作記憶體共享功能,比如數學上的pi就可以存在靜態欄位),一般來說欄位應該帶有private 或者 protected訪問屬性,一般來說欄位需要通過類中的方法,或者屬性來暴露給其他類,通過限制間接訪問內部欄位,來保護輸入資料的安全,
屬性的本質是類的一個成員,它提供了私有欄位讀,寫,計算值的靈活機制,屬性如果是資料成員能直接被使用,但本質是特殊方法,我們稱之為訪問器,它的作用是使得資料訪問更容易,資料更安全,方法訪問更靈活,屬性使得類暴露了get,set公有方法,它隱藏了實作,get屬性訪問器,用來回傳屬性值,set屬性訪問器,用來設定值,綜上,屬性的本質是一對方法的包裝,get,set,
他們是完全不同的語言元素,欄位是類里保存資料的基本單元(變數),屬性不能保存,
需要創建屬性來控制私有變數(欄位)基于物件的讀寫訪問控制,
一個欄位給其他類訪問,只有兩種方法,欄位的訪問屬性修改為public,不建議,因為欄位是可讀可寫的,無法阻止用戶寫某些欄位,比如出生日期,只讀不可寫,使用屬性,
欄位不能拋出例外,呼叫方法,屬性可以,
在屬性里, Set 或者 Get 方法由編譯器預定義好了,
2. 欄位,屬性與方法的IL代碼
2.1 C#代碼
主程式
class Program
{
static void Main(string[] args)
{
Person Tom = new Person();
Tom.SayHello();
Console.WriteLine("{0}", Tom.Name);
}
}
Person類
public class Person
{
private string _name;
public string _firstName;
public string Name
{
get
{
// return _name;
return "Hello";
}
set
{
_name = value;
}
}
public int Age{get;private set;} //AutoProperty generates private field for us
public void SayHello()
{
Console.WriteLine("Hello World!");
}
}
2.2 IL代碼分析
2.2.1 欄位的IL代碼
可以看到欄位的IL代碼的關鍵字是 field,
.field private string _name
.field public string _firstName
2.2.2 屬性的IL代碼
2.2.2.1 屬性
屬性的IL關鍵字即是property,
.property instance string Name()
{
.get instance string FieldPropertyMethod.Person::get_Name()
.set instance void FieldPropertyMethod.Person::set_Name(string)
} // end of property Person::Name
點到對應的get,set訪問器,
.method public hidebysig specialname instance string
get_Name() cil managed
{
.maxstack 1
.locals init (
[0] string V_0
)
IL_0000: nop
IL_0001: ldstr "Hello"
IL_0006: stloc.0 // V_0
IL_0007: br.s IL_0009
IL_0009: ldloc.0 // V_0
IL_000a: ret
} // end of method Person::get_Name
.method public hidebysig specialname instance void
set_Name(
string 'value'
) cil managed
{
.maxstack 8
IL_0000: nop
IL_0001: ldarg.0 // this
IL_0002: ldarg.1 // 'value'
IL_0003: stfld string FieldPropertyMethod.Person::_name
IL_0008: ret
} // end of method Person::set_Name
從上可以看出get,set訪問器的本質就是方法(method).由上屬性就是對get,set兩種方法及其訪問特性的封裝,由此可見,屬性就是對get,set方法的封裝,
2.2.2.2 自動生成屬性
a. 自動生成屬性代碼
代碼量小,實用,此語法從C#3.0開始定義自動屬性
public int Age{get;private set;}
b. 自動生成屬性的IL代碼分析
.property instance int32 Age()
{
.get instance int32 FieldPropertyMethod.Person::get_Age()
.set instance void FieldPropertyMethod.Person::set_Age(int32)
} // end of property Person::Age
} // end of class FieldPropertyMethod.Person
由上可以看出,其IL代碼證明也是屬性,繼續看get,set欄位屬性方法,
.method public hidebysig specialname instance int32
get_Age() cil managed
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
= (01 00 00 00 )
.maxstack 8
IL_0000: ldarg.0 // this
IL_0001: ldfld int32 FieldPropertyMethod.Person::'<Age>k__BackingField'
IL_0006: ret
} // end of method Person::get_Age
.method private hidebysig specialname instance void
set_Age(
int32 'value'
) cil managed
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
= (01 00 00 00 )
.maxstack 8
IL_0000: ldarg.0 // this
IL_0001: ldarg.1 // 'value'
IL_0002: stfld int32 FieldPropertyMethod.Person::'<Age>k__BackingField'
IL_0007: ret
} // end of method Person::set_Age
k__BackingField 即是屬性背后的欄位變數,這是編譯器自動生成的后臺欄位,由此自動屬性與我們自己定義的屬性功能一模一樣,
2.2.3 方法的IL代碼分析
IL代碼中的關鍵字method即表示方法,
.method public hidebysig instance void
SayHello() cil managed
{
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Hello World!"
IL_0006: call void [System.Console]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ret
} // end of method Person::SayHello
備注:本IL代碼由rider的IL View功能產生
3 屬性的功能
3.1 設定只讀屬性
像出生年月這種只讀不能寫的屬性,易用屬性,
public datetime birthday{get;private set;}
3.2 呼叫方法
在屬性Count中呼叫CalculateNoOfRows方法;
public class Rows
{
private string _count;
public int Count
{
get
{
return CalculateNoOfRows();
}
}
public int CalculateNoOfRows()
{
// Calculation here and finally set the value to _count
return _count;
}
}
3.3 賴加載
有些資料加載的功能可以放在屬性中加載,不放在建構式中,以此來加快物件創建的速度,
3.4 介面繼承
可以對介面里的屬性進行繼承,而欄位不行;
3.5 屬性做個簡單的校驗
class Name
{
private string MFullName="";
private int MYearOfBirth;
public string FullName
{
get
{
return(MFullName);
}
set
{
if (value=https://www.cnblogs.com/JerryMouseLi/archive/2020/10/22/=null)
{
throw(new InvalidOperationException("Error !"));
}
MFullName=value;
}
}
public int YearOfBirth
{
get
{
return(MYearOfBirth);
}
set
{
if (MYearOfBirth<1900 || MYearOfBirth>DateTime.Now.Year)
{
throw(new InvalidOperationException("Error !"));
}
MYearOfBirth=value;
}
}
public int Age
{
get
{
return(DateTime.Now.Year-MYearOfBirth);
}
}
public string FullNameInUppercase
{
get
{
return(MFullName.ToUpper());
}
}
}
例子而已,ddd中一般來說值物件來定義,校驗也同樣會放在值物件中,
3.6 屬性中呼叫事件
public class Person {
private string _name;
public event EventHandler NameChanging;
public event EventHandler NameChanged;
public string Name{
get
{
return _name;
}
set
{
OnNameChanging();
_name = value;
OnNameChanged();
}
}
private void OnNameChanging(){
NameChanging?.Invoke(this,EventArgs.Empty);
}
private void OnNameChanged(){
NameChanged?.Invoke(this,EventArgs.Empty);
}
4 欄位的優越性
欄位作為屬性的存盤基元功用之外,還有沒有應用場景是性能超越屬性的呢?答案是肯定的,欄位作為ref/out引數時,性能更優異,
下面舉一例,
4.1 屬性賦值代碼
class Program
{
static void Main(string[] args)
{
#region 屬性性能測驗
Point[] points = new Point[1000000];
Initializ(points);
var bigRunTime = DateTime.Now;
for (int i = 0; i < points.Length; i++)
{
int x = points[i].X;
int y = points[i].Y;
TransformPoint(ref x, ref y);
points[i].X = x;
points[i].Y = y;
}
var endRunTime = DateTime.Now;
var timeSpend=ExecDateDiff(bigRunTime,endRunTime);
Console.WriteLine("變換后首元素坐標:{0},{1}",points[0].X,points[0].Y);
Console.WriteLine("程式執行花費時間:{0}",timeSpend);
#endregion
}
/// 程式執行時間測驗
/// </summary>
/// <param name="dateBegin">開始時間</param>
/// <param name="dateEnd">結束時間</param>
/// <returns>回傳(秒)單位,比如: 0.00239秒</returns>
public static string ExecDateDiff(DateTime dateBegin, DateTime dateEnd)
{
TimeSpan ts1 = new TimeSpan(dateBegin.Ticks);
TimeSpan ts2 = new TimeSpan(dateEnd.Ticks);
TimeSpan ts3 = ts1.Subtract(ts2).Duration();
//你想轉的格式
return ts3.TotalMilliseconds.ToString();
}
static Point[] Initializ(Point[] points)
{
for (int i = 0; i < points.Length; i++)
{
points[i] =new Point();
points[i].X = 1;
points[i].Y = 2;
}
Console.WriteLine("首元素坐標:{0},{1}",points[0].X,points[0].Y);
return points;
}
static void TransformPoint(ref int x, ref int y)
{
x = 3;
y = 4;
}
}
public class Point
{
public int X { get; set; }
public int Y { get; set; }
}
這里屬性為什么不能直接系結ref引數呢?rider的智能提示給我們做了解答

翻譯過來的意思是屬性回傳的是臨時變數,ref需要系結特定的變數,如欄位,陣列元素等,
屬性拷貝需要的時間:

花費時間大約是31ms,
4.2 欄位賦值
class Program
{
static void Main(string[] args)
{
#region 欄位性能測驗
PointField[] points = new PointField[1000000];
InitializField(points);
var bigRunTime = DateTime.Now;
for (int i = 0; i < points.Length; i++)
{
TransformPoint(ref points[i].X, ref points[i].Y);
}
var endRunTime = DateTime.Now;
var timeSpend=ExecDateDiff(bigRunTime,endRunTime);
Console.WriteLine("變換后首元素坐標:{0},{1}",points[0].X,points[0].Y);
Console.WriteLine("欄位賦值執行花費時間:{0}",timeSpend);
#endregion
}
/// 程式執行時間測驗
/// </summary>
/// <param name="dateBegin">開始時間</param>
/// <param name="dateEnd">結束時間</param>
/// <returns>回傳(秒)單位,比如: 0.00239秒</returns>
public static string ExecDateDiff(DateTime dateBegin, DateTime dateEnd)
{
TimeSpan ts1 = new TimeSpan(dateBegin.Ticks);
TimeSpan ts2 = new TimeSpan(dateEnd.Ticks);
TimeSpan ts3 = ts1.Subtract(ts2).Duration();
//你想轉的格式
return ts3.TotalMilliseconds.ToString();
}
static PointField[] InitializField(PointField[] points)
{
for (int i = 0; i < points.Length; i++)
{
points[i] =new PointField();
points[i].X = 1;
points[i].Y = 2;
}
Console.WriteLine("首元素坐標:{0},{1}",points[0].X,points[0].Y);
return points;
}
static void TransformPoint(ref int x, ref int y)
{
x = 3;
y = 4;
}
}
public class PointField
{
public int X;
public int Y;
}

綜上,使用欄位的性能比使用屬性性能提升了38.7%(31-19/31=38.7%),很可觀,
究其原因,屬性開辟了臨時變數作為中轉進行了深拷貝,而欄位則是直接對地址(指標)進行解參考,直接賦值,
出賦值速度提升外,欄位不需開辟臨時記憶體,更加節省記憶體,
5 小技巧
在vs中prop 按tab鍵可自動生成屬性
6 ref參考的本質
寫在文末,也算是本文的彩蛋,該方法的形參通過關鍵字ref將變數設定成了參考,
static void TransformPoint(ref int x, ref int y)
{
x = 3;
y = 4;
}
參考ref的IL代碼
.method private hidebysig static void
TransformPoint(
int32& x,
int32& y
) cil managed
對沒錯,你看到了&,熟悉C語言的道友知道,在這里是取了傳入整形變數的地址,所以在方法里進行解參考賦值,就能改變形參的值,
本質就是通過指標(傳入變數的地址)來對形參值的修改,
gitHub代碼地址
參考文章:
What is the difference between a field and a property?
著作權宣告:本文為博主原創文章,遵循 CC 4.0 BY-SA 著作權協議,轉載請附上原文出處鏈接和本宣告, 本文鏈接:https://www.cnblogs.com/JerryMouseLi/p/13855733.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/184888.html
標籤:.NET技术
