有没有一种方法只在C#中设置属性
我正在寻找一种方法来允许C#对象中的属性只能设置一次。 编写代码很容易,但我宁愿使用标准的机制(如果存在的话)。
public OneShot <int> SetOnceProperty {get; 组; }
我想要发生的是该属性可以设置,如果它尚未设置,但抛出一个例外,如果它已经被设置之前。 它应该像一个Nullable值,我可以检查,看看它是否已经设置或不。
.NET 4.0中的TPL对此有直接的支持; 直到那时只是做检查你自己…这不是很多线路,从我记得…
就像是:
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; }
}
然后使用任何构造函数。
正如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; } }
然后你可以使用这个作为你的特定财产的支持。
在C#中没有这样的function(从3.5开始)。 你必须自己编码。
你有没有考虑过只读? 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; } }
这是我的承担:
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提供了这个function,我认为 TaskCompletionSource<T>
可能是他的意思,但我不能确定。
我解决scheme的一些不错的属性:
-
TaskCompletionSource<T>
是官方支持的MS类,简化了实现。 - 您可以select同步或asynchronous获取值。
- 这个类的一个实例将隐含地转换为它所存储的值的types。 当需要传递值时,这可以稍微清理一下你的代码。
你可以做到这一点,但不是一个明确的解决scheme,代码可读性不是最好的。 如果你正在做代码devise,你可以看看与AOP串联的单例实现来拦截 setter。 实现只是123 🙂
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或基元的types。 例如,Stringtypes实现了ICloneable。 那么你会返回一个克隆的数据或新的实例,而不是实际的数据。
generics仅用于基元:T GetObject where T:struct;
如果您知道获取对数据的引用的对象将永远不会覆盖它,则这不是必需的。
另外,请考虑ReadOnlyCollection是否适用于您的应用程序。 每当对数据进行更改时都会引发exception。