I'm looking for a way to allow a property in a C# object to be set once only. It's easy to write the code to do this, but I would rather use a standard mechanism if one exists.
我正在寻找一种方法来允许C#对象中的属性只设置一次。编写代码很容易,但我宁愿使用标准机制(如果存在)。
public OneShot<int> SetOnceProperty { get; set; }
What I want to happen is that the property can be set if it is not already set, but throw an exception if it has been set before. It should function like a Nullable value where I can check to see if it has been set or not.
我想要发生的是,如果属性尚未设置,则可以设置属性,但如果之前已设置,则抛出异常。它应该像Nullable值一样运行,我可以检查它是否已设置。
12 个解决方案
#1
43
There is direct support for this in the TPL in .NET 4.0;
在.NET 4.0的TPL中直接支持这一点;
(edit: the above sentence was written in anticipation of System.Threading.WriteOnce<T>
which existed in the "preview" bits available at the time, but this seems to have evaporated before the TPL hit RTM/GA)
(编辑:上面的句子是为了预期System.Threading.WriteOnce
until then just do the check yourself... it isn't many lines, from what I recall...
直到那时才自己做检查......从我记得的内容来看,并不多。
something like:
就像是:
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; }
}
Then use, for example:
然后使用,例如:
readonly WriteOnce<string> name = new WriteOnce<string>();
public WriteOnce<string> Name { get { return name; } }
#2
26
You can roll your own (see the end of the answer for a more robust implementation that is thread safe and supports default values).
您可以自己滚动(请参阅答案的结尾,以获得更安全的线程安全实现并支持默认值)。
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;
}
}
You can use it like so:
您可以像这样使用它:
public class Foo
{
private readonly SetOnce<int> toBeSetOnce = new SetOnce<int>();
public int ToBeSetOnce
{
get { return toBeSetOnce; }
set { toBeSetOnce.Value = value; }
}
}
More robust implementation below
下面更强大的实现
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
11
This can be done with either fiddling with flag:
这可以通过摆弄旗帜来完成:
private OneShot<int> setOnce;
private bool setOnceSet;
public OneShot<int> SetOnce
{
get { return setOnce; }
set
{
if(setOnceSet)
throw new InvalidOperationException();
setOnce = value;
setOnceSet = true;
}
}
which is not good since you can potentially receive a run-time error. It's much better to enforce this behavior at compile-time:
这是不好的,因为您可能会收到运行时错误。在编译时强制执行此行为要好得多:
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;
}
}
and then use either constructor.
然后使用任一构造函数。
#4
4
As Marc said there is no way to do this by default in .Net but adding one yourself is not too difficult.
正如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;
}
}
You can then use this as the backing for your particular property.
然后,您可以将其用作特定属性的支持。
#5
3
No such feature in C# (as of 3.5). You have to code it yourself.
C#中没有这样的功能(从3.5开始)。你必须自己编写代码。
#6
2
Have you considered readonly? http://en.csharp-online.net/const,_static_and_readonly
你有没有考虑过只读? http://en.csharp-online.net/const,_static_and_readonly
It's only available to set during init, but might be what you are looking for.
它只能在init期间设置,但可能正是您要找的。
#7
1
/// <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
1
Here's my take on this:
这是我对此的看法:
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's answer suggests the TPL provides this functionality and I think TaskCompletionSource<T>
might have been what he meant, but I can't be sure.
Marc的回答表明TPL提供了这个功能,我认为TaskCompletionSource
Some nice properties of my solution:
我解决方案的一些不错的属性:
-
TaskCompletionSource<T>
is an officially support MS class which simplifies the implementation. -
TaskCompletionSource
是一个官方支持的MS类,它简化了实现。 - You can choose to synchronously or asynchronously get the value.
- 您可以选择同步或异步获取值。
- An instance of this class will implicitly convert to the type of value it stores. This can tidy up your code a little bit when you need to pass the value around.
- 此类的实例将隐式转换为它存储的值的类型。当你需要传递值时,这可以稍微整理你的代码。
#9
0
You can do this but is not a clear solution and code readability is not the best. If you are doing code design you can have a look at singleton realization in tandem with AOP to intercept setters. The realization is just 123 :)
您可以这样做,但不是一个明确的解决方案,代码可读性不是最好的。如果您正在进行代码设计,您可以查看与AOP串联的单例实现来拦截setter。实现只是123 :)
#10
0
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
}
}
Notice that myFoo is declared as an IFoo, but instantiated as a Foo.
请注意,myFoo被声明为IFoo,但实例化为Foo。
This means that Bar can be set within the initializer block, but not through a later reference to myFoo.
这意味着可以在初始化程序块中设置Bar,但不能通过稍后对myFoo的引用来设置。
#11
0
The answers assume that objects that receive a reference to an object in the future will not try to change it. If you want to protect against this, you need to make your write-once code only work for types that implement ICloneable or are primitives. the String type implements ICloneable for example. then you would return a clone of the data or new instance of the primitive instead of the actual data.
答案假设将来接收对象引用的对象不会尝试更改它。如果您想要防止这种情况,您需要使您的一次编写代码仅适用于实现ICloneable或基元的类型。例如,String类型实现了ICloneable。那么你将返回数据的克隆或原语的新实例而不是实际数据。
Generics for primitives only: T GetObject where T: struct;
仅用于基元的泛型:T GetObject其中T:struct;
This is not needed if you know that objects that get a reference to the data will never overwrite it.
如果您知道获取对数据的引用的对象将永远不会覆盖它,则不需要这样做。
Also, consider if the ReadOnlyCollection will work for your application. an exception is thrown whenever a change is attempted on the data.
另外,请考虑ReadOnlyCollection是否适用于您的应用程序。每当尝试对数据进行更改时,都会抛出异常。
#12
0
While the accepted and top-rated answers most directly answer this (older) question, another strategy would be to build a class hierarchy such that you can construct children via parents, plus the new properties:
虽然接受和评价最高的答案最直接地回答了这个(较旧的)问题,但另一个策略是建立一个类层次结构,以便您可以通过父项构建子项,以及新属性:
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;
}
}
By relying on constructors, you can spray some Febreeze on the code smell, though it's still tedious and a potentially expensive strategy.
通过依赖构造函数,你可以在代码气味上喷洒一些Febreeze,尽管它仍然是乏味的并且是一种潜在的昂贵策略。
#1
43
There is direct support for this in the TPL in .NET 4.0;
在.NET 4.0的TPL中直接支持这一点;
(edit: the above sentence was written in anticipation of System.Threading.WriteOnce<T>
which existed in the "preview" bits available at the time, but this seems to have evaporated before the TPL hit RTM/GA)
(编辑:上面的句子是为了预期System.Threading.WriteOnce
until then just do the check yourself... it isn't many lines, from what I recall...
直到那时才自己做检查......从我记得的内容来看,并不多。
something like:
就像是:
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; }
}
Then use, for example:
然后使用,例如:
readonly WriteOnce<string> name = new WriteOnce<string>();
public WriteOnce<string> Name { get { return name; } }
#2
26
You can roll your own (see the end of the answer for a more robust implementation that is thread safe and supports default values).
您可以自己滚动(请参阅答案的结尾,以获得更安全的线程安全实现并支持默认值)。
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;
}
}
You can use it like so:
您可以像这样使用它:
public class Foo
{
private readonly SetOnce<int> toBeSetOnce = new SetOnce<int>();
public int ToBeSetOnce
{
get { return toBeSetOnce; }
set { toBeSetOnce.Value = value; }
}
}
More robust implementation below
下面更强大的实现
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
11
This can be done with either fiddling with flag:
这可以通过摆弄旗帜来完成:
private OneShot<int> setOnce;
private bool setOnceSet;
public OneShot<int> SetOnce
{
get { return setOnce; }
set
{
if(setOnceSet)
throw new InvalidOperationException();
setOnce = value;
setOnceSet = true;
}
}
which is not good since you can potentially receive a run-time error. It's much better to enforce this behavior at compile-time:
这是不好的,因为您可能会收到运行时错误。在编译时强制执行此行为要好得多:
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;
}
}
and then use either constructor.
然后使用任一构造函数。
#4
4
As Marc said there is no way to do this by default in .Net but adding one yourself is not too difficult.
正如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;
}
}
You can then use this as the backing for your particular property.
然后,您可以将其用作特定属性的支持。
#5
3
No such feature in C# (as of 3.5). You have to code it yourself.
C#中没有这样的功能(从3.5开始)。你必须自己编写代码。
#6
2
Have you considered readonly? http://en.csharp-online.net/const,_static_and_readonly
你有没有考虑过只读? http://en.csharp-online.net/const,_static_and_readonly
It's only available to set during init, but might be what you are looking for.
它只能在init期间设置,但可能正是您要找的。
#7
1
/// <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
1
Here's my take on this:
这是我对此的看法:
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's answer suggests the TPL provides this functionality and I think TaskCompletionSource<T>
might have been what he meant, but I can't be sure.
Marc的回答表明TPL提供了这个功能,我认为TaskCompletionSource
Some nice properties of my solution:
我解决方案的一些不错的属性:
-
TaskCompletionSource<T>
is an officially support MS class which simplifies the implementation. -
TaskCompletionSource
是一个官方支持的MS类,它简化了实现。 - You can choose to synchronously or asynchronously get the value.
- 您可以选择同步或异步获取值。
- An instance of this class will implicitly convert to the type of value it stores. This can tidy up your code a little bit when you need to pass the value around.
- 此类的实例将隐式转换为它存储的值的类型。当你需要传递值时,这可以稍微整理你的代码。
#9
0
You can do this but is not a clear solution and code readability is not the best. If you are doing code design you can have a look at singleton realization in tandem with AOP to intercept setters. The realization is just 123 :)
您可以这样做,但不是一个明确的解决方案,代码可读性不是最好的。如果您正在进行代码设计,您可以查看与AOP串联的单例实现来拦截setter。实现只是123 :)
#10
0
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
}
}
Notice that myFoo is declared as an IFoo, but instantiated as a Foo.
请注意,myFoo被声明为IFoo,但实例化为Foo。
This means that Bar can be set within the initializer block, but not through a later reference to myFoo.
这意味着可以在初始化程序块中设置Bar,但不能通过稍后对myFoo的引用来设置。
#11
0
The answers assume that objects that receive a reference to an object in the future will not try to change it. If you want to protect against this, you need to make your write-once code only work for types that implement ICloneable or are primitives. the String type implements ICloneable for example. then you would return a clone of the data or new instance of the primitive instead of the actual data.
答案假设将来接收对象引用的对象不会尝试更改它。如果您想要防止这种情况,您需要使您的一次编写代码仅适用于实现ICloneable或基元的类型。例如,String类型实现了ICloneable。那么你将返回数据的克隆或原语的新实例而不是实际数据。
Generics for primitives only: T GetObject where T: struct;
仅用于基元的泛型:T GetObject其中T:struct;
This is not needed if you know that objects that get a reference to the data will never overwrite it.
如果您知道获取对数据的引用的对象将永远不会覆盖它,则不需要这样做。
Also, consider if the ReadOnlyCollection will work for your application. an exception is thrown whenever a change is attempted on the data.
另外,请考虑ReadOnlyCollection是否适用于您的应用程序。每当尝试对数据进行更改时,都会抛出异常。
#12
0
While the accepted and top-rated answers most directly answer this (older) question, another strategy would be to build a class hierarchy such that you can construct children via parents, plus the new properties:
虽然接受和评价最高的答案最直接地回答了这个(较旧的)问题,但另一个策略是建立一个类层次结构,以便您可以通过父项构建子项,以及新属性:
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;
}
}
By relying on constructors, you can spray some Febreeze on the code smell, though it's still tedious and a potentially expensive strategy.
通过依赖构造函数,你可以在代码气味上喷洒一些Febreeze,尽管它仍然是乏味的并且是一种潜在的昂贵策略。