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