使用反射(DotNET)查找程序集中的所有命名空间

20

我有一个已经以ReflectionOnly方式加载的程序集,我想查找该程序集中的所有命名空间,以便将它们转换为自动生成的源代码文件模板中的“using”(在VB中称为“Imports”)语句。

理想情况下,我只想限制在顶级命名空间内搜索,而不是:

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

你只会得到:

using System;

我注意到System.Type类上有一个Namespace属性,但是是否有更好的方法可以收集程序集中的命名空间,而不涉及迭代所有类型并剔除重复的命名空间字符串呢?

非常感谢, David


1
以防您未收到通知 - 我的答案下有一条关于如何在.NET 2.0上使用Linq的新评论。 - Daniel Earwicker
6个回答

40

很抱歉,这个没有捷径,尽管使用LINQ相对容易。例如,在C#中原始的“命名空间集合”将如下所示:

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

要获取顶级命名空间,您应该编写一个方法:

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);
}

我很好奇为什么你只需要顶级命名空间...这似乎是一个奇怪的限制。

3
注意命名空间可能为空;也许需要一些null-coalescing /过滤。但除此之外...该死的,你又赢了(再次);-p - Marc Gravell
1
好的,对于空值的处理你说得很好。不过我们似乎对“仅限顶层”的限制有不同的理解,注意一下。 - Jon Skeet
不,我只是编码错误了(请参见已删除帖子上的评论)- 我的版本仅适用于具有类型的顶级命名空间。 - Marc Gravell
注意:要首先获取“assembly”变量,您可以使用以下代码:var assembly = Assembly.LoadFile(@"c:\path\to\my\assembly.dll"); - JohnLBevan
1
@JohnLBevan:问题的前提是汇编已经被加载。在其他情况下,我会将汇编视为现有类型的一部分。 - Jon Skeet
显示剩余2条评论

4

命名空间只是类型名称中的一种命名约定,因此它们仅作为在许多限定的类型名称中重复出现的模式而“存在”。因此,您必须循环遍历所有类型。不过,可以将此代码编写为单个 Linq 表达式。


谢谢Earwicker。Linq不可用(仍在使用DotNET 2.0),但在所有类型上进行迭代只需要大约20行非linq代码。 - David Rutten
1
你一定要看看BclExtras - http://code.msdn.microsoft.com/BclExtras - 它提供了扩展方法属性和大多数IEnumerable扩展的定义,以有条件编译的块形式。因此,您可以在这些答案中使用任何Linq代码,但仍然针对.NET 2.0。 - Daniel Earwicker

2

这里提供了一种类似于LINQ的方法,本质上仍然是迭代每个元素,但代码更加简洁。

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

如果你正在自动生成代码,最好完全限定所有内容,这样你就不必担心在生成的代码中出现命名冲突。


@Josh,感谢提供示例。代码是自动生成的,但会暴露给用户使用。因此,我不想在源代码中添加数百个导入和使用语句。但由于典型的附加程序集只有几个命名空间,我认为包括它们所有可能确实是一个好主意。 - David Rutten

2
一点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);
}

1
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());
}

1

你别无选择,只能迭代所有类。

请注意,导入不会递归工作。例如,“using System”不会导入来自子命名空间(如System.Collections或System.Collections.Generic)的任何类,而是必须全部包含。


谢谢CodyManix,我想我会提供一个选项,允许递归命名空间包含。对于大多数额外的程序集来说,这并不是什么大问题,因为它们没有那么多的命名空间。 - David Rutten

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接