Frage

zeigte ich diese Struktur zu einem Kollegen Programmierer und sie glaubten, dass es sich um eine veränderbare Klasse sein sollte. Sie hielten es ist unbequem, nicht null Referenzen zu haben und die Möglichkeit, das Objekt zu verändern, je nach Bedarf. Ich möchte wirklich wissen, ob es irgendwelche anderen Gründe sind dies ein veränderliches Klasse zu machen.

[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
}
War es hilfreich?

Lösung

Ein Programm, das manipuliert eine Telefonnummer ist ein Modell eines Prozesses.

Daher machen Dinge, die in dem Prozess unveränderlich im Code unveränderlich sind. Die Dinge, die in dem Prozess wandelbar im Code wandelbar sind.

Zum Beispiel kann ein Prozess umfasst wahrscheinlich eine Person. Eine Person hat einen Namen. Eine Person kann ihren Namen ändern, während ihre Identität behalten. Daher sollte der Name einer Person Objekt wandelbar sein.

Eine Person hat eine Telefonnummer. Eine Person kann ihre Telefonnummer ändern, während ihre Identität behalten. Daher sollte die Telefonnummer einer Person wandelbar sein.

Eine Telefonnummer hat eine Vorwahl. Eine Telefonnummer kann seinen NICHT Vorwahl ändern und seine Identität behalten; Sie ändern die Ortsvorwahl, haben Sie jetzt eine andere Telefonnummer haben. Deshalb ist die Vorwahl einer Telefonnummer sollte unveränderlich sein.

Andere Tipps

ich denke, es ist in Ordnung es als unveränderliche Struktur zu halten - aber ich persönlich würde nur getrennte Variablen für jeden der logischen Felder verwenden, es sei denn, Sie haben werden große Zahlen von diesen im Speicher eine Zeit. Wenn Sie die am besten geeignete Art halten (z ushort für 3-4 Ziffern), dann sollte es nicht sein , die teuer -. Und der Code wird ein verdammt viel klarer sein

Ich bin damit einverstanden, dass dies ein unveränderlicher Typ sein. Aber warum diese Struktur sollte eine ICloneable und IEquatable Schnittstelle implementieren? Es ist ein Werttyp.

Ich persönlich das Gefühl, dass dies als eine unveränderliche Struktur verläßt, ist eine sehr gute Sache. Ich würde nicht empfehlen, es auf ein veränderbares Klasse zu ändern.

Die meisten der Zeit, in meiner Erfahrung, wollen Menschen unveränderlich structs zu vermeiden, tun dies aus Faulheit. Unveränderliche structs zwingen Sie die Struktur neu erstellen wird die volle Parameter, aber gut Konstruktorüberladungen enorm hier helfen kann. (Ein Beispiel, Blick auf diesen Font-Konstruktor - auch wenn es eine Klasse, es implementiert ein „Klon alles, aber diese Variable“ -Muster, dass Sie für Ihre gemeinsamen Felder duplizieren können, die geändert werden müssen.)

Erstellen von wandelbaren Klassen einleiten andere Fragen und Overhead, dass ich, wenn dies erforderlich vermeiden würde.

Vielleicht ist Ihr Mitarbeiter durch eine Reihe von Methoden erfüllt werden könnten einzelne Felder zu ermöglichen, leicht „geändert“ werden (was eine neue Instanz mit den gleichen Werten wie die erste Instanz mit Ausnahme für das neue Feld).

public PhoneNumber ApplyAreaCode(int areaCode)
{
  return new PhoneNumber(
    areaCode, 
    centralOfficeCode, 
    subscriberNumber, 
    extension);
}

Auch Sie könnten einen Sonderfall haben für eine „unbestimmt“ Telefonnummer:

public static PhoneNumber Empty
{ get {return default(PhoneNumber); } }

public bool IsEmpty
{ get { return this.Equals(Empty); } }

Die „Empty“ Eigenschaft gibt eine natürlichere Syntax als entweder „default (Phone) oder eine neue Phone ()“ und ermöglicht das Äquivalent eines NULL-Prüfung entweder mit „foo == PhoneNumber.Empty“ oder foo.IsEmpty.

Auch ... In Ihrem TryParse Sie nicht, dass Sie auf

return result.value != 0;

NULL-Zulässigkeit kann einfach über Phone behandelt werden?

Datenhalter, die abschnittsweise veränderbare sein werden sollten Strukturen sein, anstatt Klassen. Während einer Debatte, ob Strukturen sollten abschnittsweise veränderbare sein, wandelbar Klassen machen miese Daten Halter .

Das Problem ist, dass jedes Klassenobjekt effektiv enthält zwei Arten von Informationen:

  1. Der Inhalt aller seiner Felder
  2. Der Verbleib aller Referenzen, die es existieren

Wenn ein Objekt der Klasse unveränderlich ist, wird es im Allgemeinen keine Rolle, was Verweise darauf vorhanden sind. Wenn ein Datenhalteklassenobjekt wandelbar ist, jedoch werden alle Verweise auf sie wirksam „angebracht“ zueinander sind; jede Mutation auf einem von ihnen durchgeführt wird effektiv auf alle durchgeführt werden.

Wenn PhoneNumber eine veränderbare Struktur, so könnte man die Felder von einem Lagerort des Typ PhoneNumber ohne Felder dieses Typs in einem anderen Speicherort zu beeinflussen ändern. Wenn man var temp = Customers("Fred").MainPhoneNumber; temp.Extension = "x431"; Customers("Fred").MainPhoneNumber = temp; sagen, die ohne Auswirkungen auf jemanden andere Freds Erweiterung ändern würde. Wenn dagegen PhoneNumber ein veränderliches Klasse wäre, würde der obige Code setzt die Erweiterung für alle, dessen MainPhoneNumber einen Verweis auf das gleiche Objekt gehalten, aber nicht die Erweiterung von jedermann, deren MainPhoneNumber identischen Daten gehalten beeinflusst aber nicht das gleiche Objekt. Icky.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top