views:

510

answers:

6

Hi,

I've got an assembly (loaded as ReflectionOnly) and I want to find all the namespaces in this assembly so I can convert them into "using" ("Imports" in VB) statements for an auto-generated source code file template.

Ideally I'd like to restrict myself to top-level namespaces only, so instead of:

using System;
using System.Collections;
using System.Collections.Generic;

you'd only get:

using System;

I noticed there is a Namespace property on the System.Type class, but is there a better way to collect Namespaces inside an assembly that doesn't involve iterating over all types and culling duplicate namespace strings?

Much obliged, David

+4  A: 

Namespaces are really just a naming convention in type names, so they only "exist" as a pattern that is repeated across many qualified type names. So you have to loop through all the types. However, the code for this can probably be written as a single Linq expression.

Daniel Earwicker
Thanks Earwicker. Linq is out of reach (still working on DotNET 2.0) but iterating over all types only takes about 20 lines of code in non-linq.
David Rutten
You should definitely check out BclExtras - http://code.msdn.microsoft.com/BclExtras - it provides definitions for the extension method attribute and most of the `IEnumerable` extensions, in conditionally compiled blocks. So you can use any of the Linq code in these answers and yet still target .NET 2.0.
Daniel Earwicker
+1  A: 

You will have no other choice than iterating over all classes.

Note that imports don't work recursively. "using System" won't import any classes from subnamespaces like System.Collections or System.Collections.Generic, instead you must include them all.

codymanix
Thanks CodyManix, I think I'll offer an option that allows for recursive namespace inclusion as well. With most additional assemblies it's no big deal since they don't have that many namespaces anyway.
David Rutten
+7  A: 

No, there's no shortcut for this, although LINQ makes it relatively easy. For example, in C# the raw "set of namespaces" would be:

var namespaces = assembly.GetTypes()
                         .Select(t => t.Namespace)
                         .Distinct();

To get the top-level namespace instead you should probably write a method:

var topLevel = assembly.GetTypes()
                       .Select(t => GetTopLevelNamespace(t))
                       .Distinct();

...

static string GetTopLevelNamespace(Type t)
{
    string ns = t.Namespace ?? "";
    int firstDot = ns.IndexOf('.');
    return firstDot == -1 ? ns : ns.Substring(0, firstDot);
}

I'm intrigued as to why you only need top level namespaces though... it seems an odd constraint.

Jon Skeet
Watch out that namespace can be null; maybe some null-coalescing / filtering. But otherwise... darn, you beat me (again) ;-p
Marc Gravell
Good call on the nullity. I see we've understood the "top level only" constraint differently, mind you.
Jon Skeet
No, I simply coded it wrong (see comment on deleted post) - my version would only have worked for top-level namespaces with a type in.
Marc Gravell
Thanks Jon, interesting approach. I only want to offer the top-level namespaces as defaults, people can always chose to add additional using statements.
David Rutten
+1  A: 

Here's a sort of linq'ish way, it still in essence is itterating over every element but the code is much cleaner.

var nameSpaces = from type in Assembly.GetExecutingAssembly().GetTypes()
                 select  type.Namespace;
nameSpaces = nameSpaces.Distinct();

Also if your auto generating code, you might be better off to fully qualify everything, then you won't have to worry about naming conflicts in the generated code.

JoshBerke
@Josh, thanks for the sample. The code is auto-generated but then exposed to the user. So I don't want to pollute the source with hundreds of Imports and using statements. But since the typical added assembly only has a few namespaces I think it's probably indeed a good idea to have the option to include them all.
David Rutten
+1  A: 

A bit of LINQ?

var qry = (from type in assembly.GetTypes()
           where !string.IsNullOrEmpty(type.Namespace)
           let dotIndex = type.Namespace.IndexOf('.')
           let topLevel = dotIndex < 0 ? type.Namespace
                : type.Namespace.Substring(0, dotIndex)
           orderby topLevel
           select topLevel).Distinct();
foreach (var ns in qry) {
    Console.WriteLine(ns);
}
Marc Gravell
+1  A: 
public static void Main() {

    var assembly = ...;

    Console.Write(CreateUsings(FilterToTopLevel(GetNamespaces(assembly))));
}

private static string CreateUsings(IEnumerable<string> namespaces) {
    return namespaces.Aggregate(String.Empty,
                                (u, n) => u + "using " + n + ";" + Environment.NewLine);
}

private static IEnumerable<string> FilterToTopLevel(IEnumerable<string> namespaces) {
    return namespaces.Select(n => n.Split('.').First()).Distinct();
}

private static IEnumerable<string> GetNamespaces(Assembly assembly) {
    return (assembly.GetTypes().Select(t => t.Namespace)
            .Where(n => !String.IsNullOrEmpty(n))
            .Distinct());
}
nullptr