More Effective C# 02.可變動的資料優先使用隱藏屬性 More Effective C# 02.可變動的資料優先使用隱藏屬性(Prefer Implicit Properties for Mutable Data)

這個做法提到了 Implicit Properties 但實際上就是上一個做法提到的 automatically implemented properties, 並且對 Implicit Properties 進行詳細描述。

在上一個做法有提到並不是每個屬性都需要額外的存取檢查,其實大部分的都是單純的存取操作而已,所以可以改用 Implicit Properties 增加程式碼可讀性。

public class User
{
	public string Name { get; set; }
}

使用反編譯軟體可以看出它實際上就是自動建立一個私有欄位(backing field)然後對它做存取操作而已,注意到這個欄位的名稱是編譯器幫忙產生的, 所以在我們寫程式當下這個欄位是不存在的,所以沒辦法直接操作這個欄位,同樣只能透過屬性進行存取。

public class User
{
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private string <Name>k__BackingField;

    public string Name
    {
        [CompilerGenerated]
        get
        {
            return <Name>k__BackingField;
        }
        [CompilerGenerated]
        set
        {
            <Name>k__BackingField = value;
        }
    }
}

跟一般的屬性一樣 Implicit Properties 能夠對存取子進行額外的存取範圍的修飾。

public string Name
{
	get;
	protected set;
}
public string Name
{
	get;
	internal set;
}
public string Name
{
	get;
	protected internal set;
}
public string Name
{
	get;
	private set;
}

public string Name { get; }

另外在繼承的場合,新的衍生類別可以使用 base 存取基底類別的欄位,要注意這裡雖然使用了 override 但是 backing field 還是會生成在 BaseType 類別內部,而不是在 DerivedType,也就是說我們不用在 DerivedType 建立一個私有的欄位來存放資料, 可以直接用基底類別的 backing field 就好。

public class BaseType
{
	public virtual string Name
	{
		get;
		protected set;
	}
}
public class DerivedType : BaseType
{
	public override string Name
	{
		get => base.Name;
		protected set
		{
			if (!string.IsNullOrEmpty(value))
				base.Name = value;
		}
	}
}

使用 Implicit Properties 可以讓你在增加驗證或者其它功能的時候能夠保持 binary-compatible 也就是不用重新編譯使用到這個屬性的外部類別, 並且可以集中把邏輯都放在同一個地方。

例如我在一開始開發的時候只追求方便快速那麼就只採用最簡單的寫法。

public class Person
{
	public string FirstName { get; set; }
	public string LastName { get; set; }
	public override string ToString() => $"{FirstName} {LastName}";
}

但是之後可以直接對屬性追加額外的判斷邏輯,這樣並不會把使用到這個類別的外部類別重新編譯,而且又能把邏輯都集中到這個屬性裡面。

public class Person
{
	public Person(string firstName, string lastName)
	{
		this.FirstName = firstName;
		this.LastName = lastName;
	}
	
	private string firstName;
	public string FirstName
	{
		get => firstName;
		set
		{
			if (string.IsNullOrEmpty(value))
				throw new ArgumentException("First name cannot be null or empty");
			firstName = value;
		}
	}
	private string lastName;
	public string LastName
	{
		get => lastName;
		private set
		{
			if (string.IsNullOrEmpty(value))
				throw new ArgumentException("Last name cannot be null or empty");
			lastName = value;
		}
	}
	public override string ToString() => $"{FirstName} {LastName}";
}

Summary

當資料是可變類型時建議直接使用 Auto-implemented properties,使用它可以節省開發時間並且可讀性會增加,有需要也可以直接調整並把驗證邏輯都集中在一個地方。