9

この構造体を仲間のプログラマーに見せたところ、彼らはそれが可変クラスであるべきだと感じました。彼らは、null 参照がなく、必要に応じてオブジェクトを変更できないのは不便だと感じました。これを変更可能なクラスにする他の理由があるかどうかを知りたいです。

[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
}
4

7 に答える 7

21

電話番号を操作するプログラムは、プロセスのモデルです。

したがって、プロセスで不変のものはコードで不変にします。プロセスで変更可能なものをコードで変更可能にします。

たとえば、プロセスにはおそらく人が含まれます。人には名前があります。個人は、ID を保持したまま名前を変更できます。したがって、人物オブジェクトの名前は可変でなければなりません。

人には電話番号があります。個人は、ID を保持したまま電話番号を変更できます。したがって、人の電話番号は可変でなければなりません。

電話番号には市外局番があります。電話番号は、市外局番を変更して ID を保持することはできません。市外局番を変更すると、別の電話番号になります。したがって、電話番号の市外局番は不変でなければなりません。

于 2010-03-17T23:11:51.893 に答える
8

不変の構造体として保持しても問題ないと思いますが、一度に膨大な数の論理フィールドをメモリに保持しない限り、論理フィールドごとに個別の変数を使用するだけです。最も適切なタイプ (たとえばushort3 ~ 4 桁) に固執する場合、それほどコストがかからないはずです。また、コードは非常に明確になります。

于 2010-03-17T20:49:35.447 に答える
2

これは不変の型であるべきだということに同意します。しかし、なぜこの構造体は ICLoneable および IEquatable インターフェイスを実装する必要があるのでしょうか? 値型です。

于 2010-03-17T20:50:18.247 に答える
2

個人的には、これを不変の構造体として残すことは非常に良いことだと感じています。変更可能なクラスに変更することはお勧めしません。

ほとんどの場合、私の経験では、不変の構造体を避けたい人は、怠惰からこれを行っています。不変の構造体では、完全なパラメーターで構造体を再作成する必要がありますが、優れたコンストラクターのオーバーロードは、ここで非常に役立ちます。(例として、このFont コンストラクターを見てください。これはクラスですが、変更が必要な共通フィールド用に複製できる「この変数以外のすべてを複製する」パターンを実装しています。)

可変クラスを作成すると、必要でない限り避けるべき他の問題とオーバーヘッドが発生します。

于 2010-03-17T21:07:03.983 に答える
2

おそらく、あなたの同僚は、個々のフィールドを簡単に「変更」できる一連のメソッドに満足するかもしれません (その結果、新しいフィールドを除いて最初のインスタンスと同じ値を持つ新しいインスタンスが生成されます)。

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()」よりも自然な構文を提供し、「foo == PhoneNumber.Empty」または foo.IsEmpty のいずれかで null チェックと同等のものを許可します。

また...あなたのTryParseでは、あなたはそうするつもりはありません

return result.value != 0;
于 2010-03-17T23:16:29.653 に答える
0

区分的に変更可能なデータ ホルダーは、クラスではなく構造体である必要があります。構造が区分的に変更可能であるべきかどうかについて議論することはできますが、可変クラスはお粗末なデータ ホルダーになります。

問題は、すべてのクラス オブジェクトが効果的に次の 2 種類の情報を含むことです。

  1. そのすべてのフィールドの内容
  2. それに存在するすべての参照の所在

クラス オブジェクトが不変である場合、通常、どのような参照が存在するかは問題ではありません。ただし、データ保持クラス オブジェクトが変更可能である場合、それへのすべての参照は事実上互いに「関連付け」られます。それらの 1 つで実行された任意の変更は、すべてに対して効果的に実行されます。

変更可能な構造体の場合PhoneNumber、その型の 1 つの格納場所のPhoneNumberフィールドを、その型の他の格納場所のフィールドに影響を与えることなく変更できます。var temp = Customers("Fred").MainPhoneNumber; temp.Extension = "x431"; Customers("Fred").MainPhoneNumber = temp;それが他の誰にも影響を与えずにフレッドの拡張子を変更すると言うなら。対照的にPhoneNumber、可変クラスの場合、上記のコードはMainPhoneNumber、同じオブジェクトへの参照を保持しているすべての人に拡張子を設定しますが、同じデータを保持しているが同じオブジェクトではない人の拡張子には影響しませんMainPhoneNumber。イッキー。

于 2012-08-09T16:40:20.733 に答える
0

Null可能性はPhoneNumberで簡単に処理できますか?

于 2010-03-17T23:32:28.237 に答える