这个不可变的结构应该是一个可变的类吗?
我向一位程序员同事展示了这个结构,他们认为它应该是一个可变的类。他们认为没有空引用和根据需要更改对象的能力会很不方便。我真的很想知道是否还有其他原因使其成为可变类。
[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, 0, true)
{
}
public PhoneNumber(int areaCode, int centralOfficeCode, int subscriberNumber, int extension)
: this(areaCode, centralOfficeCode, subscriberNumber, extension, true)
{
}
private PhoneNumber(int areaCode, int centralOfficeCode, int subscriberNumber, 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", 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", subscriberNumber, @"The subscriber number portion must fall between 0 and 9999.");
}
else if (extension < 0 || extension > 1073741824)
{
if (!throwException) return;
throw new ArgumentOutOfRangeException("extension", extension, @"The extension portion must fall between 0 and 1073741824.");
}
else if (areaCode.ToString()[1] == '9')
{
if (!throwException) return;
throw new ArgumentOutOfRangeException("areaCode", 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:#}", AreaCode, CentralOfficeCode, SubscriberNumber, 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,
centralOfficeCode,
subscriberNumber,
extension,
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
}
I showed this struct to a fellow programmer and they felt that it should be a mutable class. They felt it is inconvenient not to have null references and the ability to alter the object as required. I would really like to know if there are any other reasons to make this a mutable class.
[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, 0, true)
{
}
public PhoneNumber(int areaCode, int centralOfficeCode, int subscriberNumber, int extension)
: this(areaCode, centralOfficeCode, subscriberNumber, extension, true)
{
}
private PhoneNumber(int areaCode, int centralOfficeCode, int subscriberNumber, 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", 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", subscriberNumber, @"The subscriber number portion must fall between 0 and 9999.");
}
else if (extension < 0 || extension > 1073741824)
{
if (!throwException) return;
throw new ArgumentOutOfRangeException("extension", extension, @"The extension portion must fall between 0 and 1073741824.");
}
else if (areaCode.ToString()[1] == '9')
{
if (!throwException) return;
throw new ArgumentOutOfRangeException("areaCode", 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:#}", AreaCode, CentralOfficeCode, SubscriberNumber, 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,
centralOfficeCode,
subscriberNumber,
extension,
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
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

发布评论
评论(7)
也许您的同事可能会对一组允许轻松“更改”各个字段的方法感到满意(导致新实例的值与第一个实例相同,但新字段除外)。
public PhoneNumber ApplyAreaCode(int areaCode)
{
return new PhoneNumber(
areaCode,
centralOfficeCode,
subscriberNumber,
extension);
}
此外,“未定义”电话号码可能有一种特殊情况:
public static PhoneNumber Empty
{ get {return default(PhoneNumber); } }
public bool IsEmpty
{ get { return this.Equals(Empty); } }
“Empty”属性提供了比“default(PhoneNumber) 或 new PhoneNumber()”更自然的语法,并允许使用以下任一方法进行等效的 null 检查: “foo == PhoneNumber.Empty”或 foo.IsEmpty。
另外......在你的 TryParse 中你不是故意的吗
return result.value != 0;
可分段更改的数据持有者应该是结构,而不是类。虽然人们可以争论结构是否应该分段改变,但可变类会导致糟糕的数据持有者。
问题是每个类对象实际上包含两种信息:
- 其所有字段的内容
- 存在的所有引用的位置
如果类对象是不可变的,那么它的引用通常并不重要。然而,当数据保存类对象是可变的时,对它的所有引用都有效地“附加”到彼此;对其中之一进行的任何突变都将有效地对所有突变进行。
如果 PhoneNumber
是一个可变结构,则可以更改 PhoneNumber
类型的一个存储位置的字段,而不会影响该类型的任何其他存储位置中的任何字段。如果有人说 var temp = Customers("Fred").MainPhoneNumber;临时扩展名=“x431”; Customers("Fred").MainPhoneNumber = temp; 这将更改 Fred 的分机而不影响其他任何人的分机。相比之下,如果 PhoneNumber
是一个可变类,则上面的代码将为 MainPhoneNumber
持有对同一对象的引用的每个人设置分机,但不会影响任何人的分机其 MainPhoneNumber
包含相同的数据,但不是同一个对象。恶心。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
我认为将其保留为不可变结构很好 - 但我个人只会为每个逻辑字段使用单独的变量,除非您一次在内存中拥有大量这些变量。如果您坚持使用最合适的类型(例如
ushort
表示 3-4 位数字),那么它应该不会那么昂贵 - 而且代码会很多更清晰。I think it's fine to keep it as an immutable struct - but I would personally just use separate variables for each of the logical fields unless you're going to have huge numbers of these in memory at a time. If you stick to the most appropriate type (e.g.
ushort
for 3-4 digits) then it shouldn't be that expensive - and the code will be a heck of a lot clearer.