86 Stimmen

Gibt es eine Möglichkeit, eine Eigenschaft nur einmal in C# zu setzen

Ich bin auf der Suche nach einem Weg, um eine Eigenschaft in einem C#-Objekt zu ermöglichen, nur einmal festgelegt werden. Es ist einfach, den Code zu schreiben, um dies zu tun, aber ich würde lieber einen Standard-Mechanismus verwenden, wenn eine existiert.

public OneShot<int> SetOnceProperty { get; set; }

Was ich will, ist, dass die Eigenschaft festgelegt werden kann, wenn es nicht bereits festgelegt ist, aber eine Ausnahme auslösen, wenn es zuvor festgelegt wurde. Es sollte wie ein Nullable Wert funktionieren, wo ich überprüfen kann, ob es festgelegt wurde oder nicht.

64voto

Marc Gravell Punkte 970173

In der TPL von .NET 4.0 gibt es dafür direkte Unterstützung;

(edit: der obige Satz wurde in Erwartung von System.Threading.WriteOnce<T> die in den damals verfügbaren "Vorschau"-Bits vorhanden waren, aber dies scheint sich verflüchtigt zu haben, bevor die TPL RTM/GA erreichte)

bis dahin machen Sie den Check einfach selbst... es sind nicht viele Zeilen, wenn ich mich recht erinnere...

etwas wie:

public sealed class WriteOnce<T>
{
    private T value;
    private bool hasValue;
    public override string ToString()
    {
        return hasValue ? Convert.ToString(value) : "";
    }
    public T Value
    {
        get
        {
            if (!hasValue) throw new InvalidOperationException("Value not set");
            return value;
        }
        set
        {
            if (hasValue) throw new InvalidOperationException("Value already set");
            this.value = value;
            this.hasValue = true;
        }
    }
    public T ValueOrDefault { get { return value; } }

    public static implicit operator T(WriteOnce<T> value) { return value.Value; }
}

Dann verwenden Sie zum Beispiel:

readonly WriteOnce<string> name = new WriteOnce<string>();
public WriteOnce<string> Name { get { return name; } }

33voto

Michael Meadows Punkte 26846

Sie können Ihre eigene Implementierung erstellen (siehe das Ende der Antwort für eine robustere Implementierung, die thread-sicher ist und Standardwerte unterstützt).

public class SetOnce<T>
{
    private bool set;
    private T value;

    public T Value
    {
        get { return value; }
        set
        {
            if (set) throw new AlreadySetException(value);
            set = true;
            this.value = value;
        }
    }

    public static implicit operator T(SetOnce<T> toConvert)
    {
        return toConvert.value;
    }
}

Sie können ihn wie folgt verwenden:

public class Foo
{
    private readonly SetOnce<int> toBeSetOnce = new SetOnce<int>();

    public int ToBeSetOnce
    {
        get { return toBeSetOnce; }
        set { toBeSetOnce.Value = value; }
    }
}

Stabilere Implementierung unten

public class SetOnce<T>
{
    private readonly object syncLock = new object();
    private readonly bool throwIfNotSet;
    private readonly string valueName;
    private bool set;
    private T value;

    public SetOnce(string valueName)
    {
        this.valueName = valueName;
        throwIfGet = true;
    }

    public SetOnce(string valueName, T defaultValue)
    {
        this.valueName = valueName;
        value = defaultValue;
    }

    public T Value
    {
        get
        {
            lock (syncLock)
            {
                if (!set && throwIfNotSet) throw new ValueNotSetException(valueName);
                return value;
            }
        }
        set
        {
            lock (syncLock)
            {
                if (set) throw new AlreadySetException(valueName, value);
                set = true;
                this.value = value;
            }
        }
    }

    public static implicit operator T(SetOnce<T> toConvert)
    {
        return toConvert.value;
    }
}

public class NamedValueException : InvalidOperationException
{
    private readonly string valueName;

    public NamedValueException(string valueName, string messageFormat)
        : base(string.Format(messageFormat, valueName))
    {
        this.valueName = valueName;
    }

    public string ValueName
    {
        get { return valueName; }
    }
}

public class AlreadySetException : NamedValueException
{
    private const string MESSAGE = "The value \"{0}\" has already been set.";

    public AlreadySetException(string valueName)
        : base(valueName, MESSAGE)
    {
    }
}

public class ValueNotSetException : NamedValueException
{
    private const string MESSAGE = "The value \"{0}\" has not yet been set.";

    public ValueNotSetException(string valueName)
        : base(valueName, MESSAGE)
    {
    }
}

24voto

Tono Nam Punkte 31244

C# 9 hat diese Funktion eingebaut. Sie heißt Nur Setzer initialisieren

public DateTime RecordedAt { get; init; }

12voto

Anton Gogolev Punkte 109749

Dies kann entweder durch eine Änderung der Flagge erreicht werden:

private OneShot<int> setOnce;
private bool setOnceSet;

public OneShot<int> SetOnce
{
    get { return setOnce; }
    set
    {
        if(setOnceSet)
            throw new InvalidOperationException();

        setOnce = value;
        setOnceSet = true;
    }
}

was nicht gut ist, da Sie möglicherweise einen Laufzeitfehler erhalten können. Es ist viel besser, dieses Verhalten zur Kompilierzeit zu erzwingen:

public class Foo
{
    private readonly OneShot<int> setOnce;        

    public OneShot<int> SetOnce
    {
        get { return setOnce; }
    }

    public Foo() :
        this(null)
    {
    }

    public Foo(OneShot<int> setOnce)
    {
        this.setOnce = setOnce;
    }
}

und verwenden Sie dann einen der beiden Konstruktoren.

5voto

Vizu Punkte 1801

In C# gibt es diese Funktion nicht (ab 3.5). Sie müssen es selbst codieren.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X