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.

4voto

JaredPar Punkte 699699

Wie Marc sagte, gibt es keine Möglichkeit, dies standardmäßig in .Net zu tun, aber das Hinzufügen einer solchen Funktion ist nicht allzu schwierig.

public class SetOnceValue<T> { 
  private T m_value;
  private bool m_isSet;
  public bool IsSet { get { return m_isSet; }}
  public T Value { get {
    if ( !IsSet ) {
       throw new InvalidOperationException("Value not set");
    }
    return m_value;
  }
  public T ValueOrDefault { get { return m_isSet ? m_value : default(T); }}
  public SetOnceValue() { }
  public void SetValue(T value) {
    if ( IsSet ) {
      throw new InvalidOperationException("Already set");
    }
    m_value = value;
    m_isSet = true;
  }
}

Diese können Sie dann als Unterlage für Ihr spezielles Objekt verwenden.

4voto

Ronnie Overby Punkte 43323

Hier ist meine Meinung dazu:

public class ReadOnly<T> // or WriteOnce<T> or whatever name floats your boat
{
    private readonly TaskCompletionSource<T> _tcs = new TaskCompletionSource<T>();

    public Task<T> ValueAsync => _tcs.Task;
    public T Value => _tcs.Task.Result;

    public bool TrySetInitialValue(T value)
    {
        try
        {
            _tcs.SetResult(value);
            return true;
        }
        catch (InvalidOperationException)
        {
            return false;
        }
    }

    public void SetInitialValue(T value)
    {
        if (!TrySetInitialValue(value))
            throw new InvalidOperationException("The value has already been set.");
    }

    public static implicit operator T(ReadOnly<T> readOnly) => readOnly.Value;
    public static implicit operator Task<T>(ReadOnly<T> readOnly) => readOnly.ValueAsync;
}

Marcs Antwort deutet darauf hin, dass die TPL diese Funktionalität bietet und I piense en TaskCompletionSource<T> könnte er gemeint haben, aber ich bin mir nicht sicher.

Einige schöne Eigenschaften meiner Lösung:

  • TaskCompletionSource<T> ist eine offiziell unterstützte MS-Klasse, die die Implementierung vereinfacht.
  • Sie können wählen, ob Sie den Wert synchron oder asynchron abrufen wollen.
  • Eine Instanz dieser Klasse konvertiert implizit in den Typ des von ihr gespeicherten Wertes. Dies kann Ihren Code ein wenig aufräumen, wenn Sie den Wert weitergeben müssen.

3voto

Josh Winkler Punkte 166

Haben Sie eine schreibgeschützte Version in Betracht gezogen? http://en.csharp-online.net/const,_static_and_readonly

Sie kann nur während der Initialisierung eingestellt werden, könnte aber genau das sein, wonach Sie suchen.

3voto

Smagin Alexey Punkte 185
/// <summary>
/// Wrapper for once inizialization
/// </summary>
public class WriteOnce<T>
{
    private T _value;
    private Int32 _hasValue;

    public T Value
    {
        get { return _value; }
        set
        {
            if (Interlocked.CompareExchange(ref _hasValue, 1, 0) == 0)
                _value = value;
            else
                throw new Exception(String.Format("You can't inizialize class instance {0} twice", typeof(WriteOnce<T>)));
        }
    }

    public WriteOnce(T defaultValue)
    {
        _value = defaultValue;
    }

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

0voto

allonhadaya Punkte 1267
interface IFoo {

    int Bar { get; }
}

class Foo : IFoo {

    public int Bar { get; set; }
}

class Program {

    public static void Main() {

        IFoo myFoo = new Foo() {
            Bar = 5 // valid
        };

        int five = myFoo.Bar; // valid

        myFoo.Bar = 6; // compilation error
    }
}

Beachten Sie, dass myFoo als IFoo deklariert ist, aber als Foo instanziiert wird.

Das bedeutet, dass Bar innerhalb des Initialisierungsblocks gesetzt werden kann, aber nicht durch einen späteren Verweis auf myFoo.

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