Есть ли способ установить свойство только в C#
Я ищу способ разрешить свойство в объекте C# быть установленным только один раз. Для этого легко написать код, но я бы предпочел использовать стандартный механизм, если он существует.
public OneShot<int> SetOnceProperty { get; set; }
Я хочу, чтобы свойство могло быть установлено, если оно еще не установлено, но бросает исключение, если оно было установлено ранее. Он должен функционировать как нулевое значение, где я могу проверить, был ли он установлен или нет.
12 ответов:
существует прямая поддержка для этого в TPL в .NET 4.0;
(edit: выше предложение было написано в ожидании
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; } }
вы можете свернуть свой собственный (см. Конец ответа для более надежной реализации, которая является потокобезопасной и поддерживает значения по умолчанию).
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) { } }
Это может быть сделано с помощью махинаций с флагом:
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; } }а затем использовать любой конструктор.
Как сказал Марк, нет способа сделать это по умолчанию в .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; } }затем вы можете использовать это в качестве поддержки для вашего конкретного свойства.
вы рассматривали только для чтения? http://en.csharp-online.net/const,_static_and_readonly
Он доступен только во время инициализации, но может быть то, что вы ищете.
/// <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; } }
вот мой взгляд на это:
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>- это официально поддерживаемый класс MS, который упрощает реализацию.- вы можете выбрать синхронно или асинхронно получить значение.
- An экземпляр этого класса будет неявно преобразован в тип значения, которое он хранит. Это может привести в порядок ваш код немного, когда нужно передать значение вокруг.
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.
Это означает, что бар может быть установлен в блоке инициализатора, но не через более позднюю ссылку на myFoo.
ответы предполагают, что объекты, которые получают ссылку на объект в будущем, не будут пытаться его изменить. Если вы хотите защитить от этого, вам нужно сделать свой код однократной записи только для типов, которые реализуют ICloneable или являются примитивами. например, строковый тип реализует ICloneable. затем вы вернете клон данных или новый экземпляр примитива вместо фактических данных.
дженерики только для примитивов: T GetObject, где T: структуры;
Это не нужно, если вы знаете, что объекты, которые получают ссылку на данные никогда не будут перезаписывать его.
кроме того, подумайте, будет ли ReadOnlyCollection работать для вашего приложения. исключение создается при попытке изменения данных.
в то время как принятые и лучшие ответы наиболее непосредственно отвечают на этот (более старый) вопрос, другая стратегия будет заключаться в создании иерархии классов, чтобы вы могли создавать детей через родителей, а также новые свойства:
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 на запах кода, хотя это все еще утомительно и потенциально дорогая стратегия.
Comments