Есть ли способ установить свойство один раз только в C # - PullRequest
61 голосов
/ 08 мая 2009

Я ищу способ разрешить установку свойства в объекте C # только один раз. Для этого легко написать код, но я бы предпочел использовать стандартный механизм, если он существует.

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

Я хочу, чтобы это свойство могло быть установлено, если оно еще не установлено, но выдает исключение, если оно было установлено ранее. Он должен работать как значение Nullable, где я могу проверить, установлено ли оно или нет.

Ответы [ 13 ]

48 голосов
/ 08 мая 2009

Существует прямая поддержка этого в TPL в .NET 4.0;

(редактировать: приведенное выше предложение было написано в ожидании System.Threading.WriteOnce<T>, которое существовало в битах «предварительного просмотра», доступных в то время, но, похоже, оно испарилось до того, как TPL достигнет RTM / GA)

до тех пор, просто сделайте проверку сами ... это не так много строк, насколько я помню ...

что-то вроде:

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; }
}

Затем используйте, например:

readonly WriteOnce<string> name = new WriteOnce<string>();
public WriteOnce<string> Name { get { return name; } }
30 голосов
/ 08 мая 2009

Вы можете свернуть свои собственные (см. Конец ответа для более надежной реализации, которая является поточно-ориентированной и поддерживает значения по умолчанию).

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;
    }
}

Вы можете использовать его так:

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

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

Более надежная реализация ниже

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)
    {
    }
}
11 голосов
/ 08 мая 2009

Это может быть сделано либо с возня с флагом:

private OneShot<int> setOnce;
private bool setOnceSet;

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

        setOnce = value;
        setOnceSet = true;
    }
}

, что нехорошо, поскольку вы потенциально можете получить ошибку во время выполнения. Гораздо лучше применять это поведение во время компиляции:

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;
    }
}

, а затем используйте любой конструктор.

4 голосов
/ 08 мая 2009

Как сказал Марк, в .Net нет способа сделать это по умолчанию, но добавить его самостоятельно не так уж сложно.

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;
  }
}

Затем вы можете использовать это в качестве подкрепления для вашей конкретной собственности.

3 голосов
/ 24 октября 2016

Вот мой взгляд на это:

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;
}

Ответ Марка предполагает, что TPL предоставляет эту функцию, и я думаю, TaskCompletionSource<T> мог быть тем, что он имел в виду, но я не уверен.

Некоторые приятные свойства моего решения:

  • TaskCompletionSource<T> - официально поддерживаемый класс MS, который упрощает реализацию.
  • Вы можете выбрать синхронное или асинхронное получение значения.
  • Экземпляр этого класса будет неявно преобразован в тип значения, которое он хранит. Это может привести в порядок ваш код, когда вам нужно передать значение.
3 голосов
/ 08 мая 2009

Нет такой функции в C # (с 3.5). Вы должны кодировать это самостоятельно.

2 голосов
/ 02 октября 2014
/// <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;
    }
}
2 голосов
/ 03 января 2013

Рассматривали ли вы только для чтения? http://en.csharp -online.net / Const, _static_and_readonly

Доступно для установки только во время инициализации, но может быть тем, что вы ищете.

0 голосов
/ 16 марта 2019

Я создал тип, который позволяет устанавливать значение в конструкции, затем после этого значение может быть установлено / переопределено только один раз, в противном случае выдается исключение.

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

    public SetOnce(T init) =>
        this.value = init;

    public T Value
    {
        get => this.value;
        set
        {
            if (this.set) throw new AlreadySetException($"Not permitted to override {this.Value}.");
            this.set = true;
            this.value = value;
        }
    }

    public static implicit operator T(SetOnce<T> setOnce) =>
        setOnce.value;

    class AlreadySetException : Exception
    {
        public AlreadySetException(string message) : base(message){}
    }
}
0 голосов
/ 24 мая 2018

В то время как принятые и получившие наибольшее количество ответов ответы наиболее прямо отвечают на этот (более старый) вопрос, другой стратегией будет построение иерархии классов, позволяющей создавать детей через родителей, плюс новые свойства:

public class CreatedAtPointA 
{
    public int ExamplePropOne { get; }
    public bool ExamplePropTwo { get; }

    public CreatedAtPointA(int examplePropOne, bool examplePropTwo)
    {
        ExamplePropOne = examplePropOne;
        ExamplePropTwo = examplePropTwo;
    }
}

public class CreatedAtPointB : CreatedAtPointA
{
    public string ExamplePropThree { get; }

    public CreatedAtPointB(CreatedAtPointA dataFromPointA, string examplePropThree) 
        : base(dataFromPointA.ExamplePropOne, dataFromPointA.ExamplePropTwo)
    {
        ExamplePropThree = examplePropThree;
    }
}

Опираясь на конструкторы, вы можете немного опрыскивать Febreeze запахом кода, хотя это все еще утомительно и потенциально дорогая стратегия.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...