Search code examples
c#var

C# var keyword not behaving as expected


The code below loops over an XmlNodeList and prints the text content in them. It works as expected:

using System;
using System.Threading.Tasks;
using System.Xml;

namespace cabinet;
public class Program
{
    public static void Main(string[] args)
    {
        XmlNodeList nodeList = XMLUtils.GetXPathNodes("./data/xml/cabinet.xml", "//cab:numéro");
        foreach (XmlNode node in nodeList)
        {

            Console.WriteLine(node.InnerText);
        }
    }
}

However, if I change the XmlNode in the foreach loop into a var the code no longer works and it generate an error.

This does not work:

    foreach (var node in nodeList)
    {

        Console.WriteLine(node.InnerText);
    }

The error is:

'object' does not contain a definition for 'InnerText' and no accessible extension method 'InnerText' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?) 

From reading a little bit online and from the error message I guess that the compiler cannot figure out the type of the node variable so it puts object. But shouldn't this work because of polymorphism? Meaning that even if the exact type can't be found, the type Object should still work, no?

Please correct if I am wrong about the Object thing, and please explain why putting var here doesn't work.


Solution

  • As @itsme86's comment explains, it's because XmlNodeList implements IEnumerable and not IEnumerable<T>.

    Why does this matter? IEnumerable's GetEnumerator() is non-generic. This means that it isn't an enumerator for a specific type, it's an enumerator for object (because anything can be casted to object, even primitives). This means that your var is actually of type object, which lacks an InnerText member, which is why you get the error.

    foreach (XmlNode node in nodeList) does work however because foreach effectively does an explicit cast at runtime. This means that node truly is an XmlNode, so you can access its InnerText member. It's effectively equivalent to:

    foreach (object obj in nodeList) {
      XmlNode node = (XmlNode) obj;
      ...
    }
    

    You can actually prove that this is a runtime cast by making node a different type. If you instead make node a string, it would still compile:

    foreach (string node in nodeList)
    {
        Console.WriteLine(node);
    }
    

    ...but it wouldn't work at runtime:

    Unhandled exception. System.InvalidCastException: Unable to cast object of type 'System.Xml.XmlNode' to type 'System.String'.
       at cabinet.Program.Main(String[] args)
    Command terminated by signal 6
    

    If XmlNodeList did implement IEnumerable<XmlNode>, then using var instead of XmlNode would have worked fine; and the string node in nodeList example would instead throw a compile-time error:

    Cannot convert type XmlNode to string