IL角度理解C#中欄位,屬性與方法的區別
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==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語言的道友知道,在這裡是取了傳入整形變數的地址。所以在方法裡進行解引用賦值,就能改變形參的值,
本質就是通過指標(傳入變數的地址)來對形參值的修改。
參考文章:
What is the difference between a field and a property?
版權宣告:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連結和本宣告。 本文連結:https://www.cnblogs.com/JerryMouseLi/p/13855733.html