C# · 12月 31, 2021

c# – 这个不可变的结构应该是可变类吗?

我向同一个程序员展示了这个结构,他们认为它应该是一个可变类.他们觉得没有空引用和根据需要改变对象的能力是不方便的.我真的很想知道是否还有其他的原因使它成为一个可变类. [Serializable]public struct PhoneNumber : IEquatable<PhoneNumber>{ private const int AreaCodeShift = 54; private const int CentralOfficeCodeShift = 44; private const int SubscriberNumberShift = 30; private const int CentralOfficeCodeMask = 0x000003FF; private const int SubscriberNumberMask = 0x00003FFF; private const int ExtensionMask = 0x3FFFFFFF; private readonly ulong value; public int AreaCode { get { return UnmaskAreaCode(value); } } public int CentralOfficeCode { get { return UnmaskCentralOfficeCode(value); } } public int SubscriberNumber { get { return UnmaskSubscriberNumber(value); } } public int Extension { get { return UnmaskExtension(value); } } public PhoneNumber(ulong value) : this(UnmaskAreaCode(value),UnmaskCentralOfficeCode(value),UnmaskSubscriberNumber(value),UnmaskExtension(value),true) { } public PhoneNumber(int areaCode,int centralOfficeCode,int subscriberNumber) : this(areaCode,centralOfficeCode,subscriberNumber,int subscriberNumber,int extension) : this(areaCode,extension,true) { } private PhoneNumber(int areaCode,int extension,bool throwException) { value = 0; if (areaCode < 200 || areaCode > 989) { if (!throwException) return; throw new ArgumentOutOfRangeException(“areaCode”,areaCode,@”The area code portion must fall between 200 and 989.”); } else if (centralOfficeCode < 200 || centralOfficeCode > 999) { if (!throwException) return; throw new ArgumentOutOfRangeException(“centralOfficeCode”,@”The central office code portion must fall between 200 and 999.”); } else if (subscriberNumber < 0 || subscriberNumber > 9999) { if (!throwException) return; throw new ArgumentOutOfRangeException(“subscriberNumber”,@”The subscriber number portion must fall between 0 and 9999.”); } else if (extension < 0 || extension > 1073741824) { if (!throwException) return; throw new ArgumentOutOfRangeException(“extension”,@”The extension portion must fall between 0 and 1073741824.”); } else if (areaCode.ToString()[1] == ‘9’) { if (!throwException) return; throw new ArgumentOutOfRangeException(“areaCode”,@”The second digit of the area code cannot be greater than 8.”); } else { value |= ((ulong)(uint)areaCode << AreaCodeShift); value |= ((ulong)(uint)centralOfficeCode << CentralOfficeCodeShift); value |= ((ulong)(uint)subscriberNumber << SubscriberNumberShift); value |= ((ulong)(uint)extension); } } public override bool Equals(object obj) { return obj != null && obj.GetType() == typeof(PhoneNumber) && Equals((PhoneNumber)obj); } public bool Equals(PhoneNumber other) { return this.value == other.value; } public override int GetHashCode() { return value.GetHashCode(); } public override string ToString() { return ToString(PhoneNumberFormat.Separated); } public string ToString(PhoneNumberFormat format) { switch (format) { case PhoneNumberFormat.Plain: return string.Format(@”{0:D3}{1:D3}{2:D4}{3:#}”,AreaCode,CentralOfficeCode,SubscriberNumber,Extension).Trim(); case PhoneNumberFormat.Separated: return string.Format(@”{0:D3}-{1:D3}-{2:D4} {3:#}”,Extension).Trim(); default: throw new ArgumentOutOfRangeException(“format”); } } public ulong ToUInt64() { return value; } public static PhoneNumber Parse(string value) { var result = default(PhoneNumber); if (!TryParse(value,out result)) { throw new FormatException(string.Format(@”The string “”{0}”” Could not be parsed as a phone number.”,value)); } return result; } public static bool TryParse(string value,out PhoneNumber result) { result = default(PhoneNumber); if (string.IsNullOrEmpty(value)) { return false; } var index = 0; var numericPieces = new char[value.Length]; foreach (var c in value) { if (char.IsNumber(c)) { numericPieces[index++] = c; } } if (index < 9) { return false; } var numericString = new string(numericPieces); var areaCode = int.Parse(numericString.Substring(0,3)); var centralOfficeCode = int.Parse(numericString.Substring(3,3)); var subscriberNumber = int.Parse(numericString.Substring(6,4)); var extension = 0; if (numericString.Length > 10) { extension = int.Parse(numericString.Substring(10)); } result = new PhoneNumber( areaCode,false ); return result.value != 0; } public static bool operator ==(PhoneNumber left,PhoneNumber right) { return left.Equals(right); } public static bool operator !=(PhoneNumber left,PhoneNumber right) { return !left.Equals(right); } private static int UnmaskAreaCode(ulong value) { return (int)(value >> AreaCodeShift); } private static int UnmaskCentralOfficeCode(ulong value) { return (int)((value >> CentralOfficeCodeShift) & CentralOfficeCodeMask); } private static int UnmaskSubscriberNumber(ulong value) { return (int)((value >> SubscriberNumberShift) & SubscriberNumberMask); } private static int UnmaskExtension(ulong value) { return (int)(value & ExtensionMask); }}public enum PhoneNumberFormat{ Plain,Separated}解决方法 操纵电话号码的程序是一个进程的模型.

因此,使代码中不可变的东西变得不可改变.使代码中可变的进程变得可变.

例如,一个过程可能包括一个人.一个人有一个名字.一个人可以改变他们的名字,同时保留他们的身份因此,人物对象的名称应该是可变的.

一个人有一个电话号码.一个人可以改变他们的电话号码,同时保留他们的身份.因此,一个人的电话号码应该是可变的.

电话号码有区号.电话号码不能更改其区号并保留其身份;您更改区号,您现在有不同的电话号码.因此,电话号码的区号应该是不可变的.