Есть ли способ установить свойство один раз только в С#

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

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

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

Ответ 1

В TPL есть прямая поддержка в .NET 4.0; до тех пор просто сделайте проверку самостоятельно... это не много строк, из того, что я помню...

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

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

Ответ 2

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

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)
    {
    }
}

Ответ 3

Это можно сделать либо с помощью флага:

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

Как сказал Марк, нет никакого способа сделать это по умолчанию в .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;
  }
}

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

Ответ 8

Вот мой пример:

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

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

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

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

Ответ 9

Вы можете это сделать, но это не ясное решение, и читаемость кода не самая лучшая. Если вы занимаетесь разработкой кода, вы можете посмотреть singleton реализацию в тандеме с AOP до перехватывать сеттеры. Реализация - всего 123:)

Ответ 10

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

Обратите внимание, что myFoo объявляется как IFoo, но создается как Foo.

Это означает, что Bar может быть установлен внутри блока инициализатора, но не через более позднюю ссылку на myFoo.

Ответ 11

В ответах предполагается, что объекты, получающие ссылку на объект в будущем, не будут пытаться изменить его. Если вы хотите защитить от этого, вам нужно сделать код с однократной записью только для типов, которые реализуют ICloneable или являются примитивами. например, тип String реализует ICloneable. то вы вернете клон данных или новый экземпляр примитива вместо фактических данных.

Общие для примитивов:   T GetObject, где T: struct;

Это не нужно, если вы знаете, что объекты, которые получают ссылку на данные, никогда не будут перезаписывать его.

Также подумайте, будет ли ReadOnlyCollection работать для вашего приложения. исключение вызывается всякий раз, когда происходит попытка изменения данных.