.net collections

.net - ¿Es ReadOnlyCollection seguro para las hebras si no se toca la colección subyacente?



collections (4)

MSDN menciona vagamente:

Una ReadOnlyCollection <(Of <(T>)>) puede admitir varios lectores simultáneamente, siempre que la colección no se modifique. Aun así, la enumeración a través de una colección no es intrínsecamente un procedimiento seguro para subprocesos . Para garantizar la seguridad de los subprocesos durante la enumeración, puede bloquear la colección durante toda la enumeración. Para permitir que se acceda a la colección mediante varios subprocesos para leer y escribir, debe implementar su propia sincronización.

¿La siguiente colección estática pública sería segura para que múltiples hilos se repitan? Si no, ¿hay algo integrado en .NET que sea seguro? ¿Debo dejar de ReadOnlyCollection y crear una nueva copia de una colección privada para cada acceso de un captador de propiedades SomeStrings? Entiendo que podría haber un problema de interbloqueo si varios subprocesos intentaron bloquear la colección pública, pero esta es una biblioteca interna, y no puedo ver por qué querríamos hacerlo.

public static class WellKnownStrings { public static readonly ICollection<string> SomeStrings; static WellKnownStrings() { Collection<string> someStrings = new Collection<string>(); someStrings.Add("string1"); someStrings.Add("string2"); SomeStrings = new ReadOnlyCollection<string>(someStrings); } }


¿Te gustaría un poco de mecanografía fuerte con eso?

Si bien su solución fue inteligente, creo que esto puede satisfacer sus necesidades un poco mejor, especialmente con respecto a la reutilización del código.

WellKnownStrings

public class WellKnownStrings : StringEnumeration { private WellKnownStrings(string specialString) :base(specialString) { } public static IEnumerable<String> SpecialStrings { get { return GetAllStrings<WellKnownStrings>(); } } public static readonly WellKnownStrings String1 = new WellKnownStrings("SOME_STRING_1"); public static readonly WellKnownStrings String2 = new WellKnownStrings("SOME_STRING_2_SPECIAL"); public static readonly WellKnownStrings String3 = new WellKnownStrings("SOME_STRING_3_SPECIAL"); }

StringEnumeration

Esta es una clase base que he adaptado para hacer justo lo que estás describiendo.

public abstract class StringEnumeration : Enumeration { private static int _nextItemValue; private static readonly object _initializeLock = new object(); protected StringEnumeration(string stringValue) :base(0, stringValue) { if(stringValue == null) { throw new ArgumentNullException("stringValue"); } lock(_initializeLock) { _nextItemValue++; _value = _nextItemValue; } } public static IEnumerable<string> GetAllStrings<T>() where T: StringEnumeration { return GetAll<T>().Select(x => x.DisplayName); } private readonly int _value; public override int Value { get { return _value; } } public static explicit operator string(WellKnownStrings specialStrings) { return specialStrings.ToString(); } }

La clase base de enumeración

Código originalmente robado y adaptado del blog de Jimmy Bogard. Los únicos cambios que hice fueron hacer que la propiedad Value virtual en la clase derivada y que GetAll () no dependa de un new T() parámetro genérico new T() , porque los campos miembro estáticos no lo hacen. Necesito una instancia para obtener el valor de manera reflexiva.

public abstract class Enumeration : IComparable { private readonly int _value; private readonly string _displayName; protected Enumeration(int value, string displayName) { _value = value; _displayName = displayName; } public virtual int Value { get { return _value; } } public string DisplayName { get { return _displayName; } } public override string ToString() { return DisplayName; } public static IEnumerable<T> GetAll<T>() where T : Enumeration { return typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly) .Where(field => field.FieldType == typeof (T)) .Select(field => field.GetValue(null)) .Where(value =>value != null) .Cast<T>(); } public override bool Equals(object obj) { var otherValue = obj as Enumeration; if (otherValue == null) { return false; } var typeMatches = GetType().Equals(obj.GetType()); var valueMatches = _value.Equals(otherValue.Value); return typeMatches && valueMatches; } public override int GetHashCode() { return _value.GetHashCode(); } public static int AbsoluteDifference(Enumeration firstValue, Enumeration secondValue) { var absoluteDifference = Math.Abs(firstValue.Value - secondValue.Value); return absoluteDifference; } public static T FromValue<T>(int value) where T : Enumeration, new() { var matchingItem = parse<T, int>(value, "value", item => item.Value == value); return matchingItem; } public static T FromDisplayName<T>(string displayName) where T : Enumeration, new() { var matchingItem = parse<T, string>(displayName, "display name", item => item.DisplayName == displayName); return matchingItem; } private static T parse<T, K>(K value, string description, Func<T, bool> predicate) where T : Enumeration, new() { var matchingItem = GetAll<T>().FirstOrDefault(predicate); if (matchingItem == null) { var message = string.Format("''{0}'' is not a valid {1} in {2}", value, description, typeof(T)); throw new Exception(message); } return matchingItem; } public int CompareTo(object other) { return Value.CompareTo(((Enumeration)other).Value); } } public static IEnumerable<T> GetAll<T>() where T : Enumeration, new() { var type = typeof(T); var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly).Where(field=>); foreach (var info in fields) { var instance = new T(); var locatedValue = info.GetValue(instance) as T; if (locatedValue != null) { yield return locatedValue; } } } public override bool Equals(object obj) { var otherValue = obj as Enumeration; if (otherValue == null) { return false; } var typeMatches = GetType().Equals(obj.GetType()); var valueMatches = _value.Equals(otherValue.Value); return typeMatches && valueMatches; } public override int GetHashCode() { return _value.GetHashCode(); } public static int AbsoluteDifference(Enumeration firstValue, Enumeration secondValue) { var absoluteDifference = Math.Abs(firstValue.Value - secondValue.Value); return absoluteDifference; } public static T FromValue<T>(int value) where T : Enumeration, new() { var matchingItem = parse<T, int>(value, "value", item => item.Value == value); return matchingItem; } public static T FromDisplayName<T>(string displayName) where T : Enumeration, new() { var matchingItem = parse<T, string>(displayName, "display name", item => item.DisplayName == displayName); return matchingItem; } private static T parse<T, K>(K value, string description, Func<T, bool> predicate) where T : Enumeration, new() { var matchingItem = GetAll<T>().FirstOrDefault(predicate); if (matchingItem == null) { var message = string.Format("''{0}'' is not a valid {1} in {2}", value, description, typeof(T)); throw new Exception(message); } return matchingItem; } public int CompareTo(object other) { return Value.CompareTo(((Enumeration)other).Value); } }

Además, sobre el tema seguro de hilo ...

La clase que proporcioné segura para subprocesos, intuitiva y reutilizable. Su caso de uso de ReadOnlyCollection<T> también fue seguro para la ejecución de subprocesos, PERO (como señala herzmeister der welten), este no es el caso en muchos escenarios. Tampoco expone en realidad a los miembros de ICollection que se pueden escribir, porque las llamadas a esos lanzan excepciones.


En general, un objeto inmutable que nunca cambia su estado interno (una vez publicado para personas que llaman desde afuera) puede verse como seguro para subprocesos.

Sin embargo, una ReadOnlyCollection<T> no es segura para subprocesos, ya que es un simple envoltorio alrededor de una colección existente que su propietario podría modificar en cualquier momento.

Sin embargo, el ejemplo en el OP es seguro para subprocesos, porque la colección subyacente no se puede modificar (al menos no sin piratear).


Si alguien está interesado en saber lo que terminé haciendo aquí, después de ver esta respuesta de Jon Skeet (por supuesto), fui con esto:

public static class WellKnownStrings { public const string String1= "SOME_STRING_1"; public const string String2= "SOME_STRING_2_SPECIAL"; public const string String3= "SOME_STRING_3_SPECIAL"; public static IEnumerable<string> SpecialStrings { get { yield return String2; yield return String3; } } }

No le da a las personas que llaman el resto de la funcionalidad ICollection<T> , pero eso no es necesario en mi caso.


Yo diría que ConcurrentCollection<T> de Extensiones paralelas haría el truco ¿verdad? Siempre se puede decir que nadie puede agregar ningún elemento a las colecciones (una colección publicitada) y usted está configurado. luke