2

Since C# has no generic implementation of the OrderedDictionary at the time of asking this question I downloaded one from here. To be very clear I am using this in the Unity game engine with MonoDevelop to code a game.

The implementation seems nicely put together however it gives me an ambiguous method call warning the solution to which I can't seem to figure out. Can somebody please explain me what is going on here and propose a possible solution to get rid of the warnings?

To be specific here are the similar method calls:

IDictionaryEnumerator IOrderedDictionary.GetEnumerator()
{
    return Dictionary.GetEnumerator();
}

IDictionaryEnumerator IDictionary.GetEnumerator()
{
    return Dictionary.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
    return List.GetEnumerator();
}

IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey,TValue>>.GetEnumerator()
{
    return List.GetEnumerator();
}

And here is the error:

[Warning] [CS0278] `TurboLabz.Game.IOrderedDictionary<string,TurboLabz.Game.RoomInfo>' contains ambiguous implementation of `enumerable' pattern.
Method `System.Collections.Specialized.IOrderedDictionary.GetEnumerator()' is ambiguous with method `System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string,TurboLabz.Game.RoomInfo>>.GetEnumerator()'

Thanks in advance.

Edit:

Here is the source and its usage in the codebase that I have:

IOrderedDictionary.cs

using System.Collections.Generic;
using System.Collections.Specialized;

namespace TurboLabz.Game
{
    public interface IOrderedDictionary<TKey, TValue> : IOrderedDictionary, IDictionary<TKey, TValue>
    {
        new int Add(TKey key, TValue value);
        void Insert(int index, TKey key, TValue value);

        new TValue this[int index]
        {
            get;
            set;
        }
    }
}

OrderedDictionary.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;

namespace TurboLabz.Game
{
    public class OrderedDictionary<TKey, TValue> : IOrderedDictionary<TKey, TValue>
    {
        private const int DefaultInitialCapacity = 0;

        private static readonly string _keyTypeName = typeof(TKey).FullName;
        private static readonly string _valueTypeName = typeof(TValue).FullName;
        private static readonly bool _valueTypeIsReferenceType = !typeof(ValueType).IsAssignableFrom(typeof(TValue));

        private Dictionary<TKey, TValue> _dictionary;
        private List<KeyValuePair<TKey, TValue>> _list;
        private IEqualityComparer<TKey> _comparer;
        private object _syncRoot;
        private int _initialCapacity;

        public OrderedDictionary()
            : this(DefaultInitialCapacity, null)
        {
        }

        public OrderedDictionary(int capacity)
            : this(capacity, null)
        {
        }

        public OrderedDictionary(IEqualityComparer<TKey> comparer)
            : this(DefaultInitialCapacity, comparer)
        {
        }

        public OrderedDictionary(int capacity, IEqualityComparer<TKey> comparer)
        {
            if(0 > capacity)
                throw new ArgumentOutOfRangeException("capacity", "'capacity' must be non-negative");

            _initialCapacity = capacity;
            _comparer = comparer;
        }

        private static TKey ConvertToKeyType(object keyObject)
        {
            if(null == keyObject)
            {
                throw new ArgumentNullException("key");
            }
            else
            {
                if(keyObject is TKey)
                    return (TKey)keyObject;
            }
            throw new ArgumentException("'key' must be of type " + _keyTypeName, "key");
        }

        private static TValue ConvertToValueType(object value)
        {
            if(null == value)
            {
                if(_valueTypeIsReferenceType)
                    return default(TValue);
                else
                    throw new ArgumentNullException("value");
            }
            else
            {
                if(value is TValue)
                    return (TValue)value;
            }
            throw new ArgumentException("'value' must be of type " + _valueTypeName, "value");
        }

        private Dictionary<TKey, TValue> Dictionary
        {
            get
            {
                if(null == _dictionary)
                {
                    _dictionary = new Dictionary<TKey, TValue>(_initialCapacity, _comparer);
                }
                return _dictionary;
            }
        }

        private List<KeyValuePair<TKey, TValue>> List
        {
            get
            {
                if(null == _list)
                {
                    _list = new List<KeyValuePair<TKey, TValue>>(_initialCapacity);
                }
                return _list;
            }
        }

        IDictionaryEnumerator IOrderedDictionary.GetEnumerator()
        {
            return Dictionary.GetEnumerator();
        }

        IDictionaryEnumerator IDictionary.GetEnumerator()
        {
            return Dictionary.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return List.GetEnumerator();
        }

        IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey,TValue>>.GetEnumerator()
        {
            return List.GetEnumerator();
        }

        public void Insert(int index, TKey key, TValue value)
        {
            if(index > Count || index < 0)
                throw new ArgumentOutOfRangeException("index");

            Dictionary.Add(key, value);
            List.Insert(index, new KeyValuePair<TKey, TValue>(key, value));
        }

        void IOrderedDictionary.Insert(int index, object key, object value)
        {
            Insert(index, ConvertToKeyType(key), ConvertToValueType(value));
        }

        public void RemoveAt(int index)
        {
            if(index >= Count || index < 0)
                throw new ArgumentOutOfRangeException("index", "'index' must be non-negative and less than the size of the collection");

            TKey key = List[index].Key;

            List.RemoveAt(index);
            Dictionary.Remove(key);
        }

        public TValue this[int index]
        {
            get
            {
                return List[index].Value;
            }

            set
            {
                if(index >= Count || index < 0)
                    throw new ArgumentOutOfRangeException("index", "'index' must be non-negative and less than the size of the collection");

                TKey key = List[index].Key;

                List[index] = new KeyValuePair<TKey, TValue>(key, value);
                Dictionary[key] = value;
            }
        }

        object IOrderedDictionary.this[int index]
        {
            get
            {
                return this[index];
            }

            set
            {
                this[index] = ConvertToValueType(value);
            }
        }

        void IDictionary<TKey, TValue>.Add(TKey key, TValue value)
        {
            Add(key, value);
        }

        public int Add(TKey key, TValue value)
        {
            Dictionary.Add(key, value);
            List.Add(new KeyValuePair<TKey,TValue>(key, value));
            return Count - 1;
        }

        void IDictionary.Add(object key, object value)
        {
            Add(ConvertToKeyType(key), ConvertToValueType(value));
        }

        public void Clear()
        {
            Dictionary.Clear();
            List.Clear();
        }

        public bool ContainsKey(TKey key)
        {
            return Dictionary.ContainsKey(key);
        }

        bool IDictionary.Contains(object key)
        {
            return ContainsKey(ConvertToKeyType(key));
        }

        bool IDictionary.IsFixedSize
        {
            get
            {
                return false;
            }
        }

        public bool IsReadOnly
        {
            get
            {
                return false;
            }
        }

        ICollection IDictionary.Keys
        {
            get
            {
                return (ICollection)Keys;
            }
        }

        public int IndexOfKey(TKey key)
        {
            if(null == key)
                throw new ArgumentNullException("key");

            for(int index = 0; index < List.Count; index++)
            {
                KeyValuePair<TKey, TValue> entry = List[index];
                TKey next = entry.Key;
                if(null != _comparer)
                {
                    if(_comparer.Equals(next, key))
                    {
                        return index;
                    }
                }
                else if(next.Equals(key))
                {
                    return index;
                }
            }

            return -1;
        }

        public bool Remove(TKey key)
        {
            if(null == key)
                throw new ArgumentNullException("key");

            int index = IndexOfKey(key);
            if(index >= 0)
            {
                if(Dictionary.Remove(key))
                {
                    List.RemoveAt(index);
                    return true;
                }
            }
            return false;
        }

        void IDictionary.Remove(object key)
        {
            Remove(ConvertToKeyType(key));
        }

        ICollection IDictionary.Values
        {
            get
            {
                return (ICollection)Values;
            }
        }

        public TValue this[TKey key]
        {
            get
            {
                return Dictionary[key];
            }
            set
            {
                if(Dictionary.ContainsKey(key))
                {
                    Dictionary[key] = value;
                    List[IndexOfKey(key)] = new KeyValuePair<TKey, TValue>(key, value);
                }
                else
                {
                    Add(key, value);
                }
            }
        }

        object IDictionary.this[object key]
        {
            get
            {
                return this[ConvertToKeyType(key)];
            }
            set
            {
                this[ConvertToKeyType(key)] = ConvertToValueType(value);
            }
        }

        void ICollection.CopyTo(Array array, int index)
        {
            ((ICollection)List).CopyTo(array, index);
        }

        public int Count
        {
            get
            {
                return List.Count;
            }
        }

        bool ICollection.IsSynchronized
        {
            get
            {
                return false;
            }
        }

        object ICollection.SyncRoot
        {
            get
            {
                if(this._syncRoot == null)
                {
                    System.Threading.Interlocked.CompareExchange(ref this._syncRoot, new object(), null);
                }
                return this._syncRoot;
            }
        }

        public ICollection<TKey> Keys
        {
            get
            {
                return Dictionary.Keys;
            }
        }

        public bool TryGetValue(TKey key, out TValue value)
        {
            return Dictionary.TryGetValue(key, out value);
        }

        public ICollection<TValue> Values
        {
            get
            {
                return Dictionary.Values;
            }
        }

        void ICollection<KeyValuePair<TKey,TValue>>.Add(KeyValuePair<TKey, TValue> item)
        {
            Add(item.Key, item.Value);
        }

        bool ICollection<KeyValuePair<TKey,TValue>>.Contains(KeyValuePair<TKey, TValue> item)
        {
            return ((ICollection<KeyValuePair<TKey,TValue>>)Dictionary).Contains(item);
        }

        void ICollection<KeyValuePair<TKey,TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            ((ICollection<KeyValuePair<TKey,TValue>>)Dictionary).CopyTo(array, arrayIndex);
        }

        bool ICollection<KeyValuePair<TKey,TValue>>.Remove(KeyValuePair<TKey, TValue> item)
        {
            return Remove(item.Key);
        }
    }
}

This is how the above given OrderedDictionary is being used:

IRoomSettingsModel.cs

namespace TurboLabz.Game
{
    public interface IRoomSettingsModel
    {
        IOrderedDictionary<string, RoomInfo> settings { get; set; }
    }
}

RoomSettingsModel.cs

namespace TurboLabz.Game
{
    public class RoomSettingsModel : IRoomSettingsModel
    {
        public IOrderedDictionary<string, RoomInfo> settings { get; set; }

        public RoomSettingsModel()
        {
            settings = new OrderedDictionary<string, RoomInfo>();
        }
    }

    public struct RoomInfo
    {
        public string id;
        public long gameDuration;
        public long prize;
    }
}

GSService.cs

namespace TurboLabz.Game
{
    public class SomeService
    {
        public IRoomSettingsModel roomSettingsModel = new RoomSettingsModel();

        public void ReadModel()
        {
            foreach (KeyValuePair<string, RoomInfo> room in roomSettingsModel.settings)
            {
                RoomInfo roomInfo = room.Value;
                Debug.Log(roomInfo.id);
            }
        }
    }
}

To keep things confidential I've changed the code a bit here but overall it should deliver the idea. The most important statement in usage above is foreach (KeyValuePair<string, RoomInfo> room in roomSettingsModel.settings) which is the source of the warning. It's in this line that I think the compiler gets confused about which GetEnumerator() method to call.

Firstly, is that really the issue? Secondly, how do I resolve the problem?

Mubeen Iqbal
  • 188
  • 3
  • 12
  • Are you looking for this `OrderedDictionary`? https://msdn.microsoft.com/en-us/library/system.collections.specialized.ordereddictionary(v=vs.110).aspx – JP Hellemons Jul 31 '17 at 12:51
  • There is an implementation on GitHub. Link and more info on this SO topic https://stackoverflow.com/questions/2629027/no-generic-implementation-of-ordereddictionary There is also one internally in the .net Framework https://stackoverflow.com/a/15689280/169714 – JP Hellemons Jul 31 '17 at 12:58
  • No, I am not looking for the `OrderedDictionary` in `System.Collections.Specialized`. I am looking for a generic version of it. And I have it too. My problem is regarding the warnings that I am getting in that code as stated in my question. – Mubeen Iqbal Jul 31 '17 at 15:52
  • @MubeenIqbal Can you include you class that inherits from `OrderedDictionary` ? I am not able to reproduce the Warning. – mayo Jul 31 '17 at 19:23
  • @mayo I have included the source in the edit in my question. I think if you iterate over the `OrderedDictionary` in a `foreach` that is when the compiler gets confused and throws a warning. – Mubeen Iqbal Aug 01 '17 at 07:30
  • Would writing an implementation of System.Collections.ObjectModel.KeyedCollection meet your requirements? It's a collection type that provides access by key or index. Only catch is it's abstract so you have to inherit it's functionality. Not sure if this is available in mono. – Ashley Pillay Aug 01 '17 at 07:41
  • @robertdeniro Interesting. I didn't know of `System.Collections.ObjectModel.KeyedCollection` data structure. It is present in Mono however it will require a lot of rework and testing to achieve the same goal which a generic implementation of OrderedDictionary is already fulfilling. The only thing I want in that is to get rid of the warning and to understand why is that warning appearing in the first place. Don't like warnings lingering in my code. – Mubeen Iqbal Aug 01 '17 at 11:00
  • by the way, looking at the problem from completly different point of view - **warning** is just warning. Sometimes you get them even if the code is OK, then, https://stackoverflow.com/questions/968293/c-sharp-selectively-suppress-custom-obsolete-warnings – quetzalcoatl Aug 04 '17 at 07:15
  • @quetzalcoatl I understand. However, this warning is not just a warning. It warns about ambiguous method calls. Specifically the `GetEnumerator()` methods. One wrong `GetEnumerator()` method gets called and whole concept of an `OrderedDictionary` gets screwed up. – Mubeen Iqbal Aug 04 '17 at 07:24

2 Answers2

4

I tried to follow what you've done, but it's a spaghetti of nested interfaces.

If you put breakpoints in every GetEnumerator() in OrderedDictionary, you may find it is not calling the enumerator you expect.

The problem, I think, is with trying to implement the non-generic IOrderedDictionary interface along with IDictionary<TKey, TValue>.

If you want generics, why do you need to maintain compatibilty with non-generic IOrderedDictionary?

If you follow (F12) the inheritance trail of IOrderedDictionary, it inherits IDictionary, ICollection, IEnumerable.

Then IDictionary<TKey, TValue> inherits from ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable.

I'm not quite sure what all your requirements are, but I would drop any interfaces you don't need to support. Don't provide code features that you don't need.

This is not completely of your making, but it is the consequence of trying to support multiple interfaces with lots of baggage of their own.

Based on your question, I would only support IDictionary<TKey, TValue> & IList<T>.

And their baggage ;)

For those curious about KeyedCollection, here's an implmentation that does most of what @Mubeen implemented in his code. This is not fully tested, so don't just do a copy->paste if you use this.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Collections.ObjectModel;

namespace TurboLabz.Game
{
    public class GenericComparer<TKey> : IComparer<TKey>
    {
        public static GenericComparer<TKey> CreateComparer(Func<TKey, TKey, int> comparer)
        {
            return new GenericComparer<TKey>(comparer);
        }

        internal GenericComparer(Func<TKey, TKey, int> comparer)
        {
            Comparer = comparer;
        }

        private Func<TKey, TKey, int> Comparer { get; set; }

        public int Compare(TKey x, TKey y)
        {
            return Comparer(x, y);
        }
    }

    public class OrderedDictionaryKC<TKey, TValue> : KeyedCollection<TKey,KeyValuePair<TKey, TValue>>
    {
        public OrderedDictionaryKC()
        { }

        public OrderedDictionaryKC(IEnumerable<KeyValuePair<TKey, TValue>> collection)
        {            
            if (collection != null)
            {
                foreach (KeyValuePair<TKey, TValue> item in collection)
                {
                    base.Add(item);
                }
            }
        }

        public OrderedDictionaryKC(IDictionary<TKey, TValue> dictionary) : this((IEnumerable<KeyValuePair<TKey, TValue>>)dictionary)
        { }

        public ICollection<TKey> Keys
        {
            get
            {
                return base.Dictionary.Keys;
            }
        }

        public ICollection<KeyValuePair<TKey, TValue>> Values
        {
            get
            {
                return base.Dictionary.Values;
            }
        }

        public void Add(TKey key, TValue value)
        {
            if (key == null)
            {
                throw new ArgumentNullException("key");
            }

            base.Add(new KeyValuePair<TKey, TValue>(key, value));
        }

        public bool ContainsKey(TKey key)
        {
            if (key == null)
            {
                throw new ArgumentNullException("key");
            }

            return base.Dictionary.ContainsKey(key);
        }

        public bool TryGetValue(TKey key, out TValue value)
        {
            KeyValuePair<TKey, TValue> outValue;
            var result=  base.Dictionary.TryGetValue(key, out outValue);
            value = outValue.Value;

            return result;
        }

        protected override TKey GetKeyForItem(KeyValuePair<TKey, TValue> item)
        {
            return item.Key;
        }
    }
}
Mubeen Iqbal
  • 188
  • 3
  • 12
Ashley Pillay
  • 868
  • 4
  • 9
  • Thanks for looking into the issue. Yes, all these unnecessary interfaces indeed are the cause of all the trouble. I've written a new implementation from scratch (took initial code from Microsoft) taking a leaner approach. It works perfect now and is warning free. So far :) I have provided the implementation below as an answer to help others out. – Mubeen Iqbal Aug 04 '17 at 07:18
2

I ended up writing a new implementation which is a pure generic wrapper around System.Collections.Specialized.OrderedDictionary.

Although this is not an answer to the original question, it is warning free and, like @ashley-pillay mentioned in his answer, implements only the necessary interfaces.

I am providing the implementation here in hopes of helping others since it's hard to find a good implementation of a warning free generic OrderedDictionary even after a lot of Googling.

IOrderedDictionary.cs

using System.Collections.Generic;

namespace TurboLabz.Common
{
    public interface IOrderedDictionary<TKey, TValue> :
        IDictionary<TKey, TValue>,
        ICollection<KeyValuePair<TKey, TValue>>,
        IEnumerable<KeyValuePair<TKey, TValue>>
    {
    }
}

OrderedDictionary.cs

//-----------------------------------------------------------------------------
// Initial code provided by Microsoft Corporation.  All rights reserved.
//-----------------------------------------------------------------------------

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;

namespace TurboLabz.Common
{
    // System.Collections.Specialized.OrderedDictionary is NOT generic.
    // This class is essentially a generic wrapper for OrderedDictionary.
    public class OrderedDictionary<TKey, TValue> : IOrderedDictionary<TKey, TValue>
    {
        private OrderedDictionary _internalDictionary;

        public OrderedDictionary()
        { 
            _internalDictionary = new OrderedDictionary();
        }

        public OrderedDictionary(IDictionary<TKey, TValue> dictionary)
        {
            if (dictionary != null)
            {
                _internalDictionary = new OrderedDictionary();

                foreach (KeyValuePair<TKey, TValue> pair in dictionary)
                {
                    _internalDictionary.Add(pair.Key, pair.Value);
                }
            }
        }

        public int Count
        {
            get
            {
                return _internalDictionary.Count;
            }
        }

        public bool IsReadOnly
        {
            get
            {
                return false;
            }
        }

        public TValue this[TKey key]
        {
            get
            {
                if (key == null)
                {
                    throw new ArgumentNullException("key");
                }

                if (_internalDictionary.Contains(key))
                {
                    return (TValue)_internalDictionary[(object)key];
                }
                else
                {
                    throw new KeyNotFoundException("Cannot find key " + key);
                }
            }

            set
            {
                if (key == null)
                {
                    throw new ArgumentNullException("key");
                }

                _internalDictionary[(object)key] = value;
            }
        }

        public ICollection<TKey> Keys
        {
            get
            {
                IList<TKey> keys = new List<TKey>(_internalDictionary.Count);

                foreach (TKey key in _internalDictionary.Keys)
                {
                    keys.Add(key);
                }

                // Keys should be put in a ReadOnlyCollection,
                // but since this is an internal class, for performance reasons,
                // we choose to avoid creating yet another collection.

                return keys;
            }
        }

        public ICollection<TValue> Values
        {
            get
            {
                IList<TValue> values = new List<TValue>(_internalDictionary.Count);

                foreach (TValue value in _internalDictionary.Values)
                {
                    values.Add(value);
                }

                // Values should be put in a ReadOnlyCollection,
                // but since this is an internal class, for performance reasons,
                // we choose to avoid creating yet another collection.

                return values;
            }
        }

        public void Add(KeyValuePair<TKey, TValue> item)
        {
            Add(item.Key, item.Value);
        }

        public void Add(TKey key, TValue value)
        { 
            if (key == null)
            { 
                throw new ArgumentNullException("key");
            }

            _internalDictionary.Add(key, value); 
        }

        public void Clear()
        {
            _internalDictionary.Clear(); 
        }

        public bool Contains(KeyValuePair<TKey, TValue> item)
        { 
            if ((item.Key == null) || !(_internalDictionary.Contains(item.Key)))
            { 
                return false; 
            }
            else
            {
                return _internalDictionary[(object)item.Key].Equals(item.Value);
            }
        }

        public bool ContainsKey(TKey key)
        {
            if (key == null)
            {
                throw new ArgumentNullException("key");
            }

            return _internalDictionary.Contains(key);
        }

        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            if (array == null)
            {
                throw new ArgumentNullException("array");
            }

            if (arrayIndex < 0)
            { 
                throw new ArgumentOutOfRangeException("arrayIndex"); 
            }

            if ((array.Rank > 1) ||
                (arrayIndex >= array.Length) ||
                ((array.Length - arrayIndex) < _internalDictionary.Count))
            {
                throw new Exception("Fx.Exception.Argument('array', SRCore.BadCopyToArray)");
            }

            int index = arrayIndex;

            foreach (DictionaryEntry entry in _internalDictionary)
            {
                array[index] = new KeyValuePair<TKey, TValue>((TKey)entry.Key, (TValue)entry.Value); 
                ++index;
            }
        }

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            foreach (DictionaryEntry entry in _internalDictionary)
            {
                yield return new KeyValuePair<TKey, TValue>((TKey)entry.Key, (TValue)entry.Value);
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public bool Remove(KeyValuePair<TKey, TValue> item)
        {
            if (Contains(item))
            {
                _internalDictionary.Remove(item.Key);
                return true;
            }
            else
            {
                return false; 
            }
        }

        public bool Remove(TKey key)
        {
            if (key == null)
            { 
                throw new ArgumentNullException("key");
            } 

            if (_internalDictionary.Contains(key))
            {
                _internalDictionary.Remove(key);
                return true;
            }
            else
            {
                return false;
            }
        }

        public bool TryGetValue(TKey key, out TValue value)
        {
            if (key == null)
            {
                throw new ArgumentNullException("key");
            }

            bool keyExists = _internalDictionary.Contains(key);
            value = keyExists ? (TValue)_internalDictionary[(object)key] : default(TValue);

            return keyExists;
        }
    }
}
Mubeen Iqbal
  • 188
  • 3
  • 12