var keyword not always working?

这一生的挚爱 提交于 2019-11-30 23:09:09

问题


C#, VS 2010. Somebody, please explain why I can't use var in my code below!

var props = TypeDescriptor.GetProperties(adapter);

// error CS1061: 'object' does not contain a definition for 'DisplayName'
foreach (var prop in props)
{
    string name = prop.DisplayName;
}

// No error
foreach (PropertyDescriptor prop in props)
{
    string name = prop.DisplayName;
}

TypeDescriptor.GetProperties returns a PropertyDescriptorCollection with instances of PropertyDescriptor. Why can't the compiler see this?


回答1:


TypeDescriptor.GetProperties returns a class that only has a GetEnumerator implementation that returns the non-generic IEnumerator. The type of its Current property is object - and that's the only type the compiler can infer, so that's the type your prop variable will be.

The second foreach that uses PropertyDescriptor instead of var as the type of prop actually performs a conversion from object to PropertyDescriptor.

Assume this code:

PropertyDescriptor x = // get from somewhere;
object o = x;
PropertyDescriptor y = (PropertyDescriptor)o;

The same is happening in the second foreach loop for each item.


You could add a Cast<PropertyDescriptor>() to get a generic IEnumerable<T> with an implementation of GetEnumerator that returns IEnumerator<T>. If you do this, you can use var in the foreach loop:

var props = TypeDescriptor.GetProperties(adapter).Cast<PropertyDescriptor>();

// No error
foreach (var prop in props)
{
    string name = prop.DisplayName;
}



回答2:


PropertyDescriptorCollection only implements IEnumerable, so the compiler only knows that elements contained in it are of type object. When you specify the type in your foreach loop, the compiler will insert a cast to the type you specify.

You can use the Cast<T> extension method on IEnumerable to cast each item to a given type:

using System.Linq;
...
IEnumerable<PropertyDescriptor> descriptors = props.Cast<PropertyDescriptor>();



回答3:


Because TypeDescriptor.GetProperties returns a PropertyDescriptorCollection which does not implement IEnumerable<PropertyDescriptor> but only IEnumerable.

Therefore prop is just an object which has no DisplayName property at compile time.

So you have to specify the type explicitely in the foreach:

foreach (PropertyDescriptor prop in props)
{
    string name = prop.DisplayName;
}



回答4:


PropertyDescriptorCollection implements IEnumerable but not IEnumerable<PropertyDescriptor> so all that can be seen is that it enumerates objects. So that's what var infers.

foreach has always had the ability to perform an hidden cast to whatever its iteration variable type is, so that's why the second version works. It had to work this way in the pre-generics era.




回答5:


What I am going to cover here is described in formal detail in the C# Language Specification, section "The foreach statement".

When you use an implicitly typed iteration variable in a foreach statement (that is, var), which is a very good thing to do, here's how the compiler finds out what var is to mean.

First it checks the compile-time type of the expression to the right-hand side of the in keyword in your foreach. (If it's an array type like PropertyDescriptor[] or PropertyDescriptor[,,,] or similar, a special rule applies. There's another rule if it's dynamic.)

It checks if that type has a method called precisely GetEnumerator (with that capitalization) with an overload that is public, non-static, non-generic and takes in zero parameters. If so, it examines the return type of this method (we're still talking compile-time types here, so it's the declared return type). This type must have a method MoveNext() and a property Current. It then takes the property type of Current and uses that as the element type. So your var stands for this type.

To show how this works, I wrote this:

    class Foreachable
    {
        public MyEnumeratorType GetEnumerator()  // OK, public, non-static, non-generic, zero arguments
        {
            return default(MyEnumeratorType);
        }

    }

    struct MyEnumeratorType
    {
        public int Current
        {
            get { return 42; }
        }

        public bool MoveNext()
        {
            return true;
        }
    }

    static class Test
    {
        static void Main()
        {
            var coll = new Foreachable();

            foreach (var x in coll)   // mouse-over 'var' to see it translates to 'int'
            {
                Console.WriteLine(x);
            }
        }
    }

You see that var becomes int (System.Int32) in my case because of the type of the Current property.

Now in your case the compile-time type of props is PropertyDescriptorCollection. The type has a GetEnumerator() which is public and non-static as required. The return type of the method is seen to be System.Collections.IEnumerator in this case. This IEnumerator type has the required Current property, and the type of this property is seen to be Object. So there it comes from!

(A lot of class originally written for .NET 1 have this design. No strong typing with foreach on them.)

Note that if a type implements IEnumerable<out T> (generic) or/and IEnumerable (non-generic), and if one of these two interfaces is implemented "implicitly" (normally, not explicit interface implementation), then the type surely has a GetEnumerator which is public and non-static, non-generic and take zero arguments. So that public method will be used by foreach.

Now if the compile-time type of the expression you're trying to foreach does not have a public instance method GetEnumerator() (no type parameters and no value parameters), the compiler will see if the type is converible to IEnumerable<Something> or (else) to IEnumerable. Since IEnumerable<out T> is covariant in T, there will often be many Something so that IEnumerable<Something> applies. This is explained a little confusingly in the spec (version 5.0). For it is also possible that the type looks like this:

    class Foreachable : IEnumerable<Animal>, IEnumerable<Giraffe>
    {
        // lots of stuff goes here
    }

for reference types Animal and Giraffe with the expected inheritance relation, and it is not clear from the spec version 5.0 that this class (compile-time type) can not be foreached.



来源:https://stackoverflow.com/questions/14874072/var-keyword-not-always-working

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!