Question

When trying to obtain a collection of types in a given compilation, I've gotten myself all confused as to which way to approach the issue. Essentially, methods that would seem to return results do not, and vice-versa. Below is output from a debug session Immediate Window, where appCompilation is of type Compilation (natch).

**appCompilation.GlobalNamespace.GetMembers()**
Count = 14
    [0]: "Namespace Registration"
    [1]: "Namespace Payments"
    [2]: "Namespace Foo"
    [3]: "NamedType <Module>"
    [4]: "NamedType <Module>"
    [5]: "NamedType <Module>"
    [6]: "NamedType <Module>"
    [7]: "NamedType <Module>"
    [8]: "NamedType <Module>"
    [9]: "NamedType <Module>"
    [10]: "Namespace Conference"
    [11]: "Namespace System"
    [12]: "NamedType <>f__AnonymousType0<<OrderId>j__TPar>"
    [13]: "Namespace Infrastructure"
**appCompilation.GlobalNamespace.GetTypeMembers()**
{System.Linq.Enumerable.OfTypeIterator<Roslyn.Compilers.CSharp.NamedTypeSymbol>}
    source: null
**appCompilation.GlobalNamespace.GetNamespaceMembers()**
{System.Linq.Enumerable.OfTypeIterator<Roslyn.Compilers.CSharp.NamespaceSymbol>}
    source: null

So my question is this: When I call .GetTypeMembers() on a Symbol of Kind == Namespace, I get null. When I call .GetNamespaceMembers() for the same symbol, I also get null. Yet, when I call .GetMembers() I get namespaces and types galore!

Weirder still, I can place this statement into a watch window and get non-empty, non-null results!

appCompilation.GlobalNamespace.GetNamespaceMembers(), results

Possibly relevant: Initiating execution of a query doesn't seem to happen when I expect, but I'm not exactly sure how that how or even why I should have to worry about that... calling .ToList() sometimes will trigger execution. I had thought that even though many methods provide a CancellationToken parameter, they all run synchronously. A problem too is that the various GetXXX() methods return either ReadOnlyArray or IEnumerable; the read-only doesn't seem to pick up the same behavior from the LINQ extension methods the same way that IEnumerable does.

Judging from a surface scan of debugging output, it looks like GetTypeMembers and its' ilk wrap GetMembers() with an .OfType<> call. Maybe it's in that translation things are getting borked?

At any rate, the inconsistency of accessing and executing queries has been quite painful, so I'm hoping that someone can help me understand what I'm missing that is making things seem so non-deterministic.

EDIT: After iterating against it a bit, I found that you just have to do a recursive search through the symbol tree, and that query syntax can be a much easier approach than lambda expressions sometimes... and oh yes - PEBKAC, since it seems that the Immediate Window was hindering more than helping debug efforts.

Final Passing query sample that retrieves all types from a given Compilation, up to a level of three namespace nestings or so (need more tests to cover those cases):

From this (and this is far better than some other attempts!)

appCompilation.Assembly.GlobalNamespace.GetNamespaceMembers()
    .SelectMany(x => x.GetNamespaceMembers().Select(y => y.GetNamespaceMembers()))
    .SelectMany(x => x, (symbols, symbol) => symbol.GetTypeMembers())
    .SelectMany(x => x);

To this (still not fully recursive, but good enough for now):

from glob in appCompilation.Assembly.GlobalNamespace
    .GetMembers()
    .OfType<NamespaceOrTypeSymbol>()
from childNs in glob
    .GetMembers()
    .OfType<NamespaceSymbol>()
from childTypes in childNs
    .GetTypeMembers()
select childTypes;
Was it helpful?

Solution

I think this is only a limitation with the Immediate window, unrelated to Roslyn.

For example with the following code:

var ints = new object[] { 2 }.OfType<int>();

I get this output in the Immediate window:

ints
{System.Linq.Enumerable.OfTypeIterator<int>}
    source: null
    System.Collections.Generic.IEnumerator<TResult>.Current: 0
    System.Collections.IEnumerator.Current: 0

But if I iterate the collection using a foreach, or use Results View in a watch window, it works correctly.

The reason you see what you do is because Enumerable.OfType() is written using an iterator block, which generates the iterator type. The iterator type has some fields with unspeakable names, including <>3__source which holds the original source (the array in my example). It also has one field with a normal name: source, which is set in the call to GetEnumerator(). Since you didn't call that method yet, source is null.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top