bernie2436
bernie2436

Reputation: 23901

why can I only access the properties of an anonymous type when I add .toList to the end of my linq query

I'm learning LINQ and VB and just spent an hour trying to access the fields of an anonymous type defined in a linq query. The key (I learned) is to convert the query to a list before you try to iterate through it with a for loop. How to access property of anonymous type in C#?

This does not work: edit (this compiles, but intellisense does not recognize the type)

Dim varbl=from itm in collct select New With {.n=itm.Name} 'query here
for each returnedItem in varbl 
     returnedItem.n 'intellisense does not pick up the property names
next 

But this does:

Dim varbl=(from itm in collct select New With {.n=itm.Name}).toList
for each returnedItem in varbl 
     returnedItem.n 'this works
next 

Can somebody explain why/what is going on? The (otherwise helpful!) post above just says it is "Because of the way the compiler works, the following then should also work once you have created the list, because the anonymous types have the same structure so they are also the same type. I don't have a compiler to hand to verify this though"

Upvotes: 0

Views: 764

Answers (1)

Jon Hanna
Jon Hanna

Reputation: 113282

Well, I wasn't going to answer since my VB is both very rusty and pretty out of date (eh, VB6 about a decade ago). But something did seem wrong here from a .NET perspective.

Your read on the answer you link to (which is incomplete in its explanation IMO) is wrong. It's not that you need to put the anonymous object into a list, but that the querent there had been putting it into a List<object> which meant that the compiler couldn't tell what properties it had (it's dealing with an object not an object of the anonymous type in question, and object doesn't have a Checked property).

Anyway. I tried writing your initial attempt into some C# as:

private class TestType
{
  public string Name{get;set;}
  public TestType(string name)
  {
    this.Name = name;
  }
}
public static void Main()
{
  TestType[] collct = new TestType[]{new TestType("abc"), new TestType("def"), new TestType("xyz")};
  var varbl = from itm in collct select new {n = itm.Name};
  foreach(var returnedItem in varbl)
    Console.WriteLine(returnedItem.n);
}

Then I compiled and decompiled as VB.NET in reflector, which had my Main as:

Public Shared Sub Main()
    Dim collct As TestType() = New TestType() { New TestType("abc"), New TestType("def"), New TestType("xyz") }
    Dim varbl = (From itm In collct Select New With { _
        .n = itm.Name _
    })
    Dim returnedItem
    For Each returnedItem In varbl
        Console.WriteLine(returnedItem.n)
    Next
End Sub

Which looks pretty much like your original, doesn't it?

Well, it works; it compiles and runs and gives me correct answers.

The only thing I can think of is that maybe IntelliSense just wasn't on the ball here. I have found that some times in the past, IntelliSense struggles to suggest the property names of anonymous types, and while I use SharpDevelop to code C# rather than VB (in whatever IDE it is that you use), this could be the case here - you actually had the correct code all along, but IntelliSense didn't realise it.

At the end of the day, IntelliSense is pretty darn good, but it's not the compiler, and it can't spend as much resources doing stuff as the compiler for risk of slowing us down while we type.

Incidentally, you should not call ToList unless you actually need a list for your purposes. Imagine if collct was a loaded-as-needed enumeration that would return thousands of objects on the back of loading something from a database or file.

If you don't call ToList, then you will start iterating through them as soon as the data for the first one arrives, and you'll only need to use memory for a handful at a time.

If you do call ToList, then program will be stuck at that line until it's loaded every single object into a massive list in memory, and only then start your For Each.

Of course, there are times when you do need to be able to move back and forward though a list, or to know the count before you iterate, and then it's certainly better to call ToList than to keep repeating the For Each, but if you don't know you need it, don't use it.

Upvotes: 2

Related Questions