Подтвердить что ты не робот

Является ли ReadOnlyCollection потокобезопасным, если базовая коллекция не затрагивается?

MSDN смутно упоминает:

ReadOnlyCollection < (Of < (T > ) > ) может поддерживать несколько считывателей одновременно, пока сбор не изменяется. Тем не менее, перечисление через коллекцию по существу не является потокобезопасной процедурой. Чтобы гарантировать безопасность потока во время перечисления, вы можете заблокировать сбор во время всего перечисления. Чтобы обеспечить доступ к коллекции несколькими потоками для чтения и записи, вы должны реализовать свою собственную синхронизацию.

Будет ли безопасен следующий публичный статический сбор для повторения нескольких потоков? Если нет, есть ли что-то встроенное в .NET, которое безопасно? Должен ли я просто отказаться от ReadOnlyCollection и создать новую копию частной коллекции для каждого доступа к элементу GetString свойства SomeStrings? Я понимаю, что может возникнуть проблема взаимоблокировки, если несколько потоков попытались заблокировать публичный сбор, но это внутренняя библиотека, и я не понимаю, почему мы когда-либо захотим.

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);
    }
}
4b9b3361

Ответ 1

Как правило, неизменяемый объект, который никогда не изменяет свое внутреннее состояние (после публикации для внешних абонентов), можно рассматривать как поточно-безопасный.

A ReadOnlyCollection<T>, однако, не является потокобезопасным как таковой, так как это простая оболочка вокруг существующей коллекции, которую ее владелец может изменять в любое время.

Пример в OP является потокобезопасным, хотя, поскольку базовая коллекция не может быть изменена (по крайней мере, не без взлома).

Ответ 2

Хотели бы вы сильную типизацию с этим?

В то время как ваше решение было умным, я думаю, что это может удовлетворить ваши потребности немного лучше, особенно в отношении повторного использования кода.

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

Это базовый класс, который я адаптировал для того, чтобы делать то, что вы описываете.

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


}

Базовый класс Enumeration

Код, изначально украденный и адаптированный из блога Jimmy Bogard Единственные изменения, которые я сделал, это сделать Value свойство virtual в производном классе и сделать GetAll() не зависимым от параметра new T() generic, потому что статические поля-члены не нуждаются в экземпляре, чтобы получить значение рефлексивно.

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

Кроме того, о проблеме с потоковой безопасностью...

Класс я предоставил потоки, интуитивно понятный и многоразовый. Ваш пример использования ReadOnlyCollection<T> также был поточно-безопасным, НО (как herzmeister der welten) указал, что это не так во многих сценариях. Он также фактически не отображает записываемых членов ICollection, поскольку любые вызовы этих исключений throw.

Ответ 3

Если кому-то интересно узнать, что я здесь сделал, увидев этот ответ от Jon Skeet (конечно), я пошел с этим:

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

Он не дает абонентам остальную часть функциональности ICollection<T>, но это не нужно в моем случае.

Ответ 4

Я бы сказал, что ConcurrentCollection<T> из Parallel extentsions сделает трюк правильно? Вы всегда можете сказать, что никто не может добавлять какие-либо предметы в коллекции (публичная коллекция) и YOu. Люк