Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Iterate over IDictionary with implicit DictionaryEntry

Tags:

c#

idictionary

Consider this code:

var variables = System.Environment.GetEnvironmentVariables();
foreach (DictionaryEntry vari in variables)
{
    Console.WriteLine(vari.Key);
    Console.WriteLine(vari.Value);
}

It works fine. Since variables is IDictionary, it consists of DictionaryEntry, with object Key and object Value.

Why cannot I type foreach(var vari in variables)? It gives me

error CS1061: 'object' does not contain a definition for 'Key/Value'...

It seems strange and I cannot find a reason for this behaviour. DictionaryEntry is a struct, but I can iterate over a List<DictionaryEntry> all right. Of course I understand that IDictionary is not generic, but the manual says it contains DictionaryEntries, so it should be possible to use var...

like image 793
Piotr Zierhoffer Avatar asked Oct 21 '13 08:10

Piotr Zierhoffer


2 Answers

Why cannot I type foreach(var vari in variables)?

Well you can - but then vari is implicitly of type object.

You happen to know that each entry within the iterator is a DictionaryEntry, but the compiler doesn't. As far as it's aware, the iteration element type of IDictionary it just object. Even though IDictionary.GetEnumerator returns an IDictionaryEnumerator, that still has a Current property with a type of object, not DictionaryEntry.

Annoyingly, this could have been done better. If IDictionaryEnumerator had been implemented using explicit interface implementation for IEnumerator.Current, and provided a new Current property of type DictionaryEntry, then this would have worked and been more efficient as it would have avoided boxing.

Section 8.8.4 of the C# spec provides the rules the C# compiler uses to determine the element type of a collection.

EDIT: For those who wanted to see how IDictionaryEnumerator could have been declared, here's a short but complete example. Note how this doesn't use generics anywhere, but does use var in Main, still with a variable implicitly typed as DictionaryEntry:

using System;
using System.Collections;

interface IJonDictionary : IEnumerable
{
    new IJonDictionaryEnumerator GetEnumerator();
}

interface IJonDictionaryEnumerator : IEnumerator
{
    new DictionaryEntry Current { get; }
}

class JonDictionary : IJonDictionary
{
    private readonly IDictionary dictionary = new Hashtable();

    public object this[object key]
    {
        get { return dictionary[key]; } 
        set { dictionary[key] = value; }
    }

    public void Add(object key, object value)
    {
        dictionary.Add(key, value);
    }

    public IJonDictionaryEnumerator GetEnumerator()
    {
        return new JonEnumerator(dictionary.GetEnumerator());
    }

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

    private class JonEnumerator : IJonDictionaryEnumerator
    {
        private readonly IDictionaryEnumerator enumerator;

        internal JonEnumerator(IDictionaryEnumerator enumerator)
        {
            this.enumerator = enumerator;
        }

        public DictionaryEntry Current
        {
            get { return enumerator.Entry; }
        }

        object IEnumerator.Current { get { return Current; } }

        public bool MoveNext()
        {
            return enumerator.MoveNext();
        }

        public void Reset()
        {
            enumerator.Reset();
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var dictionary = new JonDictionary { 
            { "x", "foo" },
            { "y", "bar" }
        };

        foreach (var entry in dictionary)
        {
            Console.WriteLine("{0} = {1}", entry.Key, entry.Value);
        }
    }
}
like image 194
Jon Skeet Avatar answered Oct 29 '22 12:10

Jon Skeet


If you don't explicitly provide the type for vari, it's considered object since variables is IEnumerable, not IEnumerable<DictionaryEntry>

//As you can imagine this won't work:
foreach (DictionaryEntry vari in variables) {
    object v2 = vari;
    Console.WriteLine(v2.Key);
    Console.WriteLine(v2.Value); 
}

//This works!:
foreach (var vari in variables) {
    DictionaryEntry v2 = (DictionaryEntry) vari;
    Console.WriteLine(v2.Key);
    Console.WriteLine(v2.Value); 
}
like image 2
Ahmed KRAIEM Avatar answered Oct 29 '22 11:10

Ahmed KRAIEM