這個做法說明了屬性跟欄位兩者容易搞混的部分,並且要預期使用者會把屬性當成欄位來使用,所以屬性不能做出太複雜最好和欄位的功能差不多。
在做法 1 與做法 2 了解到屬性實際上扮演了兩個角色,一個是保存資料元素有點像是欄位,另一個則是使用 get
、 set
存取子能夠搭配方法與檢查邏輯,
所以過度使用可能會與使用者預期的屬性會有很大的落差,畢竟有很多使用者只是把屬性當成方便使用的欄位而已,假如你的屬性包含了複雜的方法與檢查邏輯
,可能就會導致誤用。
以這段迴圈為例,其實就是不斷的在讀取 myArray
的 Length 屬性,如果這個讀取需要耗費非常多的時間,或者是進資料庫取回,
那麼這種設計就會跟使用者預期的相差很大。
for (int index = 0; index < myArray.Length; index++)
所以建議是使用做法 2 提到的 Implicit properties
讓編譯器自動實做 backing field
與輕量的存層,最多就是在存取的時候搭配輕量的檢查邏輯。
public string LastName
{
set
{
if (string.IsNullOrEmpty(value))
throw new ArgumentException("last name can't be null or blank");
lastName = value;
}
}
像是下面段程式碼中的 Distance
屬性會在回傳前進行簡單的運算也算是常用的使用方式。
public class Point
{
public int X { get; set; }
public int Y { get; set; }
public double Distance => Math.Sqrt(X * X + Y * Y);
}
但如果計算 Distance
會造成效能瓶頸,可以使用下面的寫法這樣只會進行首次運算,之後就只會讀取緩存的值。
public class Point
{
private int xValue;
public int X
{
get => xValue;
set
{
xValue = value;
distance = default(double?);
}
}
private int yValue;
public int Y
{
get => yValue;
set
{
yValue = value;
distance = default(double?);
}
}
private double? distance;
public double Distance
{
get
{
if (!distance.HasValue)
distance = Math.Sqrt(X * X + Y * Y);
return distance.Value;
}
}
}
以上都還算是合理的使用範圍,但下面這個屬性內部卻是去資料庫進行存取,這種設計會耗費大量的時間,而且使用者也可能不會有這樣的預期, 也可能在讀取的過程中發生錯誤。
public class MyType
{
public string ObjectName => RetrieveNameFromRemoteDatabase();
}
如果真的確定要這麼做那至少要搭配對應的實作模式來減少衝擊,例如下面也是使用緩存機制,這樣就只會影響第一次讀取。
public class MyType
{
private string objectName;
public string ObjectName =>
(objectName != null) ?
objectName : RetrieveNameFromRemoteDatabase();
}
這個模式也可改用 .net 提供的 Lazy<T>
類別,所以可以改寫成下面這樣,但是要先確認這個值是否緩存起來也沒關係。
private Lazy<string> lazyObjectName;
public string ObjectName => lazyObjectName.Value;
public MyType()
{
lazyObjectName = new Lazy<string>(() => RetrieveNameFromRemoteDatabase());
}
下面這對程式碼會使用 set
存取子將資料保存回資料庫內,這種操作就有可能違反使用者的預期,應該不會有人預期呼叫 set
存取子竟然要花這麼多時間吧,
另外 get
存取子也有可能在運行的期間報錯,這會導致排查變得很困難。
public class MyType
{
private string objectName;
public string ObjectName
{
get
{
if (objectName == null)
objectName = RetrieveNameFromRemoteDatabase();
return objectName;
}
set
{
objectName = value;
SaveNameToRemoteDatabase(objectName);
}
}
}
Summary
這個做法在講解屬性已經給大家帶來很快的既定印象,所以預期存取的過程都應該要相當短才是合理的,所以你的屬性違反了這些預期那應該修改屬性的實做內容, 讓屬性在使用上盡量與讀取欄位一樣快速。