17

MSDN vaguely mentions:

A ReadOnlyCollection<(Of <(T>)>) can support multiple readers concurrently, as long as the collection is not modified. Even so, enumerating through a collection is intrinsically not a thread-safe procedure. To guarantee thread safety during enumeration, you can lock the collection during the entire enumeration. To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization.

Would the following public static collection be safe for multiple threads to iterate over? If not, is there something built into .NET that is safe? Should I just drop the ReadOnlyCollection and create a new copy of a private collection for each access of a SomeStrings property getter? I understand that there could be a deadlock issue if multiple threads tried to lock on the public collection, but this is an internal library, and I can't see why we would ever want to.

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);
    }
}
Chris Marasti-Georg
  • 34,091
  • 15
  • 92
  • 137
  • 4
    +1 - Excellent question. The language used in the documentation is confusing in my opinion as well; it never clearly states if multiple threads can safely enumerate over a collection so long as that collection is not modified during that time. (I believe the answer is "it's safe" but would like to see a definitive response.) – TrueWill Nov 18 '09 at 19:51

4 Answers4

11

Generally, an immutable object that never changes its internal state (once published to callers outside) can be seen as thread-safe.

A ReadOnlyCollection<T> however is not thread-safe per se, as it is a mere wrapper around an existing collection which its owner could modify any time.

The example in the OP is thread-safe though, because the underlying collection can't be modified (at least not without hacking).

herzmeister
  • 11,101
  • 2
  • 41
  • 51
  • Thanks, that is what I thought and so when they say it may not be thread safe they are saying depending on usage. if the usage is ok, then it will be thread safe. – Sam Holder Mar 01 '11 at 11:26
  • would downvoter care to elaborate on why they downvoted this? – Sam Holder Mar 07 '11 at 10:15
  • 1
    This answers seems right to me, but I think marking it as accepted would be a bit of confirmation bias, without some citation from the language spec, or perhaps a static analysis of the code via reflector, etc. – Chris Marasti-Georg Mar 07 '11 at 14:20
5

Would you like some strong typing with that?

While your solution was clever, I think this may suit your needs a little better, especially with respect to code reuse.

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

This is a base class I've adapted for doing just what you are describing.

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


}

The Enumeration base class

Code originally stolen and adapted from Jimmy Bogard's blog The only changes I made was to make the Value property virtual in the derived class, and to make the GetAll() not dependent on a new T() generic parameter, because static member fields do not need an instance to get the value reflectively.

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

Also, about the thread safe issue...

The class I provided thread-safe, intuitive, and reusable. Your use case of ReadOnlyCollection<T> was also thread-safe, BUT (as herzmeister der welten) pointed out, this is not the case in many scenarios. It also does not actually expose the writeable ICollection members, because any calls to those throw exceptions.

smartcaveman
  • 41,281
  • 29
  • 127
  • 212
  • whilst this answer may provide a solution to the OPs specific use, I awarded the bounty for an answer to the general question, which I feel herzmeister der welten answer addresses more effectively. Thanks for getting involved though, hopefully this will help the OP or someone else that comes along. – Sam Holder Mar 07 '11 at 10:19
  • Should the "private readonly object _initializeLock = new object();" be static? Locking a member variable in a constructor does nothing... To be honest, I fail to see the benefit of your solution. Both return a read-only Enumerable of strings, and one doesn't take 15 minutes for a future maintainer to understand. – Chris Marasti-Georg Mar 07 '11 at 14:16
  • @Chris, yes - _initializeLock was supposed to be static - I changed the code to reflect this. The advantage to this approach is provides strong typing for the enumerations. So, for example let's say you want a method to take an argument that is one of the special strings. You can make the parameter type WellKnownStrings instead of string. This can be adapted for many different uses - I included an article in the post that explains some of the benefits as well – smartcaveman Mar 07 '11 at 15:45
3

If anyone is interesting in knowing what I ended up doing here, after seeing this answer by Jon Skeet (of course), I went with this:

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

It doesn't give callers the rest of the ICollection<T> functionality, but that's not needed in my case.

Community
  • 1
  • 1
Chris Marasti-Georg
  • 34,091
  • 15
  • 92
  • 137
1

I'd say ConcurrentCollection<T> from Parallel extentsions would do the trick right? You can always say that no-one can add any items to the collections (a publicized collection) and YOu are set. luke

luckyluke
  • 1,553
  • 9
  • 16