![](/img/trans.png)
[英]As per CA1008 rule of FXCop Enums should have a default value of zero. Is this applicable for C#?
[英]How to deal with enumeration 0 in C# (CA1008 discussion)
規則CA1008指定所有枚舉應該具有應該命名為Unknown
的0
值(我們不在此處討論標志)。 我理解你想要防止未初始化的值自動獲得意義的原因。 假設我定義了以下枚舉:
enum Gender
{
Male,
Female
}
class Person
{
public string Name { get; set; }
public Gender Gender { get; set; }
}
這規定每個人應該是男性或女性(暫時不要討論性別討論)。 如果我忘記設置Gender
屬性,那么此人自動為男性,這可能會導致問題。 因此,我了解CA1008警告,因此應為未知/未初始化的值保留0值。
因此,讓我們將Gender
枚舉更改為不再使用0值:
enum Gender
{
Male = 1,
Female = 2
}
當我沒有指定性別時,那個人不是男性或女性。 序列化期間可能會出現問題。 對於調試期間的枚舉,值0
不是非常具有描述性。 要修復它並避免CA1008警告,我再次更改枚舉:
enum Gender
{
Unknown = 0,
Male = 1,
Female = 2
}
未初始化的屬性現在顯示為Unknown
,看起來不錯。 但我可能已經介紹了另一個問題,那就是Unknown
值看起來像一個有效的值,可以應用於用戶。 我也可能會收到有關不處理所有枚舉值的警告。 假設,我使用構造函數,要求我指定性別和名稱以避免未初始化的屬性:
public Person(string name, Gender gender)
{
Name = name ?? throw new ArgumentNullException(name);
Gender = gender;
}
當我定義Unknown
枚舉時,我現在可以明確地將性別設置為Unknown
。 當然,這可以在構造函數中檢查,但這只會在運行時發出信號。 如果未定義Unknown
值,則調用者只能將其設置為男性或女性。
修復可能是使用可空的性別屬性,因此未初始化的值是顯式null
值(我們不再定義Unknown
值)。 但是使用可空類型會使編程變得更復雜,所以我不建議它。
將ObsoleteAttribute
應用於Unknown
值可能是個好主意。 當某人明確使用該值時,它會被標記為警告(在構建時)。
處理未初始化的枚舉值的正確方法是什么,並且使用ObsoleteAttribute
是一個好主意還是有其他缺點?
注意:*雖然過時不是正確的語義,但如果使用該值,它是唯一(簡單)生成警告的方法。 *在沒有默認構造函數的情況下使用POCO可能會使序列化變得復雜,因此在沒有它們的情況下擁有(可序列化的)類通常是個壞主意。
使用ObsoleteAttribute是個好主意?
不。使用[Obsolete]
等待... 將過時的成員標記為已廢棄 。 這是[Obsolete]
的唯一正確用法。 不要為現有的詞語發明新的含義; 這只會造成混亂。
處理未初始化的枚舉值的正確方法是什么?
這是錯誤的問題。 退后一步。 讓我們看一下你問題的大圖:
現在你被卡住了,想知道該怎么做。
你要做的是: 回到第一步到第三步,做出不同的決定 。
假設我們重新審視決策3.您已經確定了每個解決方案的一些優缺點。 決定對於其中一個,優點超過缺點,並與它一起去。
假設我們重新審視了決策2.你違反了一系列指導方針。 准則不是規則。 您可以確定指南對您的方案是不好的建議,記錄您故意違反它們的原因以及為什么,禁止警告,然后前進。
假設我們重新審視決定1.你是那個決定將性別最好地表現為枚舉的人,而且看起來這個決定已經給你帶來了相當大的痛苦。 所以拒絕這個決定:
abstract class Gender :
whatever interfaces you need for serialization and so on
{
private Gender() { } // prevent subclassing
private class MaleGender : Gender
{
// Serialization code for male gender
}
public static readonly Gender Male = new MaleGender();
// now do it all again for FemaleGender
}
我們得到了什么? 我們有Gender.Male
和Gender.Female
與之前相同,它們可以像以前一樣序列化,並且Gender
類型的任何值都是男性,女性或null。 不喜歡nulls? 拋出一個異常,就像你為這個人的名字得到一個空字符串一樣。 想要添加更多性別,例如“Unknown”或“Nonbinary”或其他什么? 添加新的子類型和靜態字段。
你沒有被迫使用枚舉。 如果滿足枚舉的准則讓您煩惱,請停止使用枚舉。
枚舉的目的是提供表示可能值的命名常量。 在您的特定設計(這是現實世界的抽象)中,一個人的性別是Male
或Female
。 沒有None
。
您的枚舉需要一個0
值成員,因為默認的底層類型是int
。 出於這個原因(並且正如編譯器指出的那樣),它應該是您的選擇之一( Male
或Female
):
public enum Gender
{
Male, //compiler defaults to 0
Female
}
要么
public enum Gender
{
Male = 0,
Female = 1
}
要么
public enum Gender
{
Male = 1,
Female = 0
}
然后,由於人必須具有性別,因此應該由抽象的構造函數強制執行值的事實:
public Person(string name, Gender gender)
{
Name = name ?? throw new ArgumentNullException(name);
Gender = gender;
}
另一方面,如果您的設計抽象出一個人可以選擇不提供性別的世界,那么您可以使用默認值表示:
public enum Gender
{
NotProvided, //compiler defaults to 0
Male,
Female
}
要么
public enum Gender
{
NotProvided = 0
Male = 1,
Female = 2
}
在這種情況下,有兩種形式的構造函數是有意義的:
public Person(string name)
{
Name = name ?? throw new ArgumentNullException(name);
}
public Person(string name, Gender gender)
{
Name = name ?? throw new ArgumentNullException(name);
Gender = gender;
}
換句話說,編譯器已經指示了正確的處理。 您只需要確保您的抽象以正確表示正在建模的方式實現它。
您的問題定義了三個我認為彼此不兼容的要求:
如果滿足1和2,那么必須引入非默認構造函數來強制執行Gender的初始化。 然后3不能滿足。
如果您滿足1和3,則必須接受用戶可能忘記初始化此屬性,並且必須引入一些內容來處理屬性具有有效值但未初始化的情況。 在許多情況下,解決方案將實現和處理未知默認值作為第三個有效值,但是不能滿足2。
如果你滿足2和3那么你會遇到問題,你必須決定性別應該默認初始化為男性或女性,也滿足#1。 如果使用默認構造函數,那么當默認選擇錯誤的一半時,您將遇到問題。
滿足所有這三個要求的唯一方法可能是將性別模型建模為人的子類型而不僅僅是屬性。
enum Gender
{
Male,
Female
}
abstract class Person
{
public string Name { get; set; }
public abstract Gender Gender { get; }
}
class MalePerson : Person
{
public override Gender Gender { get { return Gender.Male; } }
public MalePerson()
{ ... }
}
class FemalePerson : Person
{
public override Gender Gender { get { return Gender.Female; } }
public FemalePerson()
{ ... }
}
通過這種方式,您強制要求用戶必須使用Male默認構造函數或Female默認構造函數來實例化Person。 序列化還可以保留子類型並使用默認構造函數,而不會導致不正確的默認值。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.