¿Hay alguna manera de establecer una propiedad una vez solo en C #

Estoy buscando una manera de permitir que una propiedad en un objeto C # se establezca solo una vez. Es fácil escribir el código para hacer esto, pero prefiero usar un mecanismo estándar si existe.

 public OneShot  SetOnceProperty {get;  conjunto;  }

Lo que quiero que suceda es que la propiedad se puede establecer si aún no está configurada, pero arrojar una excepción si se ha establecido antes. Debería funcionar como un valor Nullable donde puedo verificar si se ha configurado o no.

Hay soporte directo para esto en el TPL en .NET 4.0;

(edit: la oración anterior fue escrita en anticipación de System.Threading.WriteOnce que existía en los bits de “vista previa” disponibles en ese momento, pero esto parece haberse evaporado antes de que TPL golpeara RTM / GA)

hasta entonces solo haz el chequeo tú mismo … no hay muchas líneas, por lo que recuerdo …

algo como:

 public sealed class WriteOnce { 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 value) { return value.Value; } } 

Luego use, por ejemplo:

 readonly WriteOnce name = new WriteOnce(); public WriteOnce Name { get { return name; } } 

Puede rodar el suyo (consulte el final de la respuesta para obtener una implementación más robusta que sea segura para subprocesos y admita valores predeterminados).

 public class SetOnce { 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 toConvert) { return toConvert.value; } } 

Puedes usarlo así:

 public class Foo { private readonly SetOnce toBeSetOnce = new SetOnce(); public int ToBeSetOnce { get { return toBeSetOnce; } set { toBeSetOnce.Value = value; } } } 

Implementación más robusta a continuación

 public class SetOnce { 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 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) { } } 

Esto se puede hacer con cualquier toque de bandera:

 private OneShot setOnce; private bool setOnceSet; public OneShot SetOnce { get { return setOnce; } set { if(setOnceSet) throw new InvalidOperationException(); setOnce = value; setOnceSet = true; } } 

lo cual no es bueno ya que potencialmente puede recibir un error en tiempo de ejecución. Es mucho mejor hacer cumplir este comportamiento en tiempo de comstackción:

 public class Foo { private readonly OneShot setOnce; public OneShot SetOnce { get { return setOnce; } } public Foo() : this(null) { } public Foo(OneShot setOnce) { this.setOnce = setOnce; } } 

y luego use cualquiera de los constructores.

Como dijo Marc, no hay forma de hacerlo por defecto en .Net, pero agregar uno no es demasiado difícil.

 public class SetOnceValue { 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; } } 

Puede usar esto como respaldo de su propiedad particular.

No hay tal característica en C # (a partir de 3.5). Tienes que codificarlo tú mismo.

¿Has considerado solo? http://en.csharp-online.net/const,_static_and_readonly

Solo está disponible para establecer durante init, pero podría ser lo que estás buscando.

 ///  /// Wrapper for once inizialization ///  public class WriteOnce { 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))); } } public WriteOnce(T defaultValue) { _value = defaultValue; } public static implicit operator T(WriteOnce value) { return value.Value; } } 

Aquí está mi opinión sobre esto:

 public class ReadOnly // or WriteOnce or whatever name floats your boat { private readonly TaskCompletionSource _tcs = new TaskCompletionSource(); public Task 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 readOnly) => readOnly.Value; public static implicit operator Task(ReadOnly readOnly) => readOnly.ValueAsync; } 

La respuesta de Marc sugiere que el TPL proporciona esta funcionalidad y creo que TaskCompletionSource podría haber sido lo que quiso decir, pero no estoy seguro.

Algunas buenas propiedades de mi solución:

  • TaskCompletionSource es una clase MS de soporte oficial que simplifica la implementación.
  • Puede elegir obtener el valor de forma sincrónica o asíncrona.
  • Una instancia de esta clase se convertirá implícitamente al tipo de valor que almacena. Esto puede arreglar tu código un poco cuando necesites pasar el valor.

Puedes hacer esto pero no es una solución clara y la legibilidad del código no es la mejor. Si está haciendo un diseño de código, puede echar un vistazo a la realización de singleton en conjunto con AOP para interceptar setters. La realización es solo 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; // comstacktion error } } 

Tenga en cuenta que myFoo está declarado como IFoo, pero creado como un Foo.

Esto significa que Bar se puede configurar dentro del bloque de inicialización, pero no a través de una referencia posterior a myFoo.

Las respuestas asumen que los objetos que reciben una referencia a un objeto en el futuro no intentarán cambiarlo. Si desea protegerse de esto, debe hacer que su código de escritura única funcione para tipos que implementen ICloneable o que sean primitivos. el tipo String implementa ICloneable por ejemplo. luego devolvería un clon de los datos o una nueva instancia de la primitiva en lugar de los datos reales.

Genéricos para primitivas solamente: T GetObject donde T: struct;

Esto no es necesario si sabes que los objetos que obtienen una referencia a los datos nunca lo sobrescribirán.

Además, considere si ReadOnlyCollection funcionará para su aplicación. se lanza una excepción cada vez que se intenta un cambio en los datos.

Si bien las respuestas aceptadas y mejor calificadas responden más directamente a esta pregunta (más antigua), otra estrategia sería construir una jerarquía de clases de modo que pueda construir hijos a través de los padres, más las nuevas propiedades:

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

Al confiar en los constructores, puede rociar Febreeze con el olor del código, aunque sigue siendo tedioso y una estrategia potencialmente costosa.