Является ли ReadOnlyCollection потокобезопасным, если базовая коллекция не затрагивается? - PullRequest
16 голосов
/ 18 ноября 2009

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

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

Будет ли следующая открытая статическая коллекция безопасной для повторения нескольких потоков? Если нет, есть ли что-то встроенное в .NET, которое безопасно? Должен ли я просто удалить ReadOnlyCollection и создать новую копию частной коллекции для каждого доступа к получателю свойства 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);
    }
}

Ответы [ 4 ]

8 голосов
/ 01 марта 2011

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

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

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

4 голосов
/ 06 марта 2011

Хотели бы вы с этим сильно печатать?

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

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


}

Базовый класс перечисления

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

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

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

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

3 голосов
/ 19 ноября 2009

Если кому-то интересно узнать, что я здесь сделал, то, увидев этот ответ Джона Скита (конечно), я согласился с этим:

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>, но в моем случае это не требуется.

1 голос
/ 07 марта 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...