Iterate over IDictionary with implicit DictionaryEntry
Asked Answered
I

3

5

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...

Incontrollable answered 21/10, 2013 at 8:38 Comment(0)
N
13

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);
        }
    }
}
Narcoanalysis answered 21/10, 2013 at 8:44 Comment(11)
This is interesting. How could it have been done better? IEnumerator calls Current from IEnumerator, so how could we provide a new implementation? I've just made an explicit implementation of IEnumerator with another Current property returning my specified class, but it still does not work the way I intended. Could you please provide some sketch solution?Incontrollable
@PiotrZierhoffer: The compiler will generate a call to Current itself - and it will call whatever Current has been provided. I would expect foreach to just do the right thing here. However, that does mean that the compiler needs to know the exact type being iterated over - you can't use IEnumerable foo = ...; foreach (var x in foo).Narcoanalysis
Jon, there is one thing I do not understand. Quote: "If IDictionaryEnumerator had been implemented using explicit interface implementation for IEnumerator.Current, and provided a new Current property of type DictionaryEntry" But how one could do that? I mean that when implementing interface (or overriding method) you can't change the type of a result, i.e. the return type is not covariant. You perhaps had something different on mind?Toms
@konrad.kruczynski: You can do exactly as I said - implement IEnumerator.Current with explicit interface implementation, and declare a separate "normal" Current property. The C# compiler would then pick it up in the foreach loop. I'll give a full example of that when I get a chance.Narcoanalysis
@JonSkeet so if I understand you correctly, it is a bad design of the IDictionaryEmulator's implementer? I'll have to play with it a bit. Would it be possible to break foreach just by leaving the explicit interface implementation and NOT providing any other overload? I'll need to take a look into documentation...Incontrollable
@JonSkeet: either I did something wrong or the method you've described does not work. The separate Current property weren't used, foreach loop utilized the explicit interface implementation... The behaviour is also consistent, when taking Piotr's comment into account.Toms
@konrad.kruczynski: It does work - see my edit with a complete example.Narcoanalysis
@PiotrZierhoffer: It's bad design of the IDictionaryEnumerator interface, IMO. There's no reason why Current can't be redeclared with a type of DictionaryEntry - just as it's redeclared as T within IEnumerable<T>. See my edit for a complete example.Narcoanalysis
@JonSkeet: now I see, the point is the new keywoard on the derived interface, I've tried to do it with a simple fully explicit IEnumerator implementation and an additional Current field. Thanks!Toms
@konrad.kruczynski: That could work as well, depending on the type that you declared GetEnumerator to return.Narcoanalysis
@JonSkeet: indeed, I finally read the spec instead of trying to guess :) The rule is not so straightforward.Toms
A
2

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); 
}
Ararat answered 21/10, 2013 at 8:44 Comment(0)
I
2

foreach loop contain explicit cast. So you'll get something along inside foreach loop:

vari = (DictionaryEntry)e.Current;

In case of object you'll get cast to object, thus making calls to Key and Value non-compiling.

From C# Spec 8.8.4 The foreach statement:

foreach (V v in x) 
  embedded-statement

is translated into:

{
    E e = ((C)(x)).GetEnumerator();
    try {
        while (e.MoveNext()) {
            V v = (V)(T)e.Current; //explicit cast
            embedded-statement
        }
    }
    finally {
        … // Dispose e
    }
}

consider Eric's article on this topic Why does a foreach loop silently insert an "explicit" conversion?

Ingmar answered 21/10, 2013 at 8:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.