问题
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 object
s. 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 foreach
ed.
来源:https://stackoverflow.com/questions/14874072/var-keyword-not-always-working