[英]Is there a way of setting a property once only in C#
我正在寻找一种方法,允许只设置一次 C# 对象中的属性。 编写代码来执行此操作很容易,但如果存在标准机制,我宁愿使用标准机制。
public OneShot<int> SetOnceProperty { get; set; }
我想要发生的是,如果尚未设置该属性,则可以对其进行设置,但如果之前已设置,则抛出异常。 它的功能应该类似于 Nullable 值,我可以在其中检查它是否已设置。
.NET 4.0 中的 TPL 对此有直接支持;
(编辑:上面的句子是在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;
}
}
然后使用任一构造函数。
public DateTime RecordedAt { get; init; }
C# 中没有这样的功能(从 3.5 开始)。 你必须自己编码。
正如 Marc 所说,在 .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;
}
}
然后,您可以将其用作您特定财产的支持。
这是我的看法:
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 类,它简化了实现。你考虑过只读吗? http://en.csharp-online.net/const,_static_and_readonly
它只能在 init 期间设置,但可能是您正在寻找的。
/// <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;
}
}
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 的引用来设置。
答案假设将来接收到对象引用的对象不会尝试更改它。 如果您想防止这种情况发生,您需要使您的一次性代码仅适用于实现 ICloneable 或原语的类型。 例如,String 类型实现了 ICloneable。 那么您将返回数据的克隆或原始数据的新实例而不是实际数据。
仅适用于原语的泛型:T GetObject where T: struct;
如果您知道获取数据引用的对象永远不会覆盖它,则不需要这样做。
此外,请考虑 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,尽管它仍然很乏味并且是一种潜在的昂贵策略。
我创建了一个类型,它允许在构造时设置一个值,然后该值只能设置/覆盖一次,否则会引发异常。
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){}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.