如何枚举所有具有自定义类属性的类?

基于 MSDN 示例的问题。

假设我们在独立的桌面应用程序中有一些具有 HelpAttribute 的 C # 类。是否可以枚举所有具有这种属性的类?这样识别类有意义吗?自定义属性将用于列出可能的菜单选项,选择项将带到屏幕实例的这种类。类/项的数量将缓慢增长,但我认为这样我们可以避免在其他地方枚举它们。

107210 次浏览

是的,当然。使用反射:

static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly) {
foreach(Type type in assembly.GetTypes()) {
if (type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0) {
yield return type;
}
}
}

那么,您必须枚举所有程序集中的所有类,这些程序集被加载到当前的应用程序域中。为此,需要为当前应用程序域调用 AppDomain实例上的 GetAssemblies

从这里开始,您可以在每个 Assembly上调用 GetExportedTypes(如果您只想要公共类型)或 GetTypes来获取程序集中包含的类型。

然后,在每个 Type实例上调用 GetCustomAttributes扩展方法,传递希望查找的属性的类型。

您可以使用 LINQ 为您简化:

var typesWithMyAttribute =
from a in AppDomain.CurrentDomain.GetAssemblies()
from t in a.GetTypes()
let attributes = t.GetCustomAttributes(typeof(HelpAttribute), true)
where attributes != null && attributes.Length > 0
select new { Type = t, Attributes = attributes.Cast<HelpAttribute>() };

上面的查询将获得应用了属性的每个类型,以及分配给它的属性的实例。

请注意,如果将大量程序集加载到应用程序域中,则该操作可能代价高昂。您可以使用 平行线性连接来减少操作时间(以 CPU 周期为代价) ,如下所示:

var typesWithMyAttribute =
// Note the AsParallel here, this will parallelize everything after.
from a in AppDomain.CurrentDomain.GetAssemblies().AsParallel()
from t in a.GetTypes()
let attributes = t.GetCustomAttributes(typeof(HelpAttribute), true)
where attributes != null && attributes.Length > 0
select new { Type = t, Attributes = attributes.Cast<HelpAttribute>() };

在特定的 Assembly上过滤它很简单:

Assembly assembly = ...;


var typesWithMyAttribute =
from t in assembly.GetTypes()
let attributes = t.GetCustomAttributes(typeof(HelpAttribute), true)
where attributes != null && attributes.Length > 0
select new { Type = t, Attributes = attributes.Cast<HelpAttribute>() };

如果程序集中有大量的类型,那么您可以再次使用并行 LINQ:

Assembly assembly = ...;


var typesWithMyAttribute =
// Partition on the type list initially.
from t in assembly.GetTypes().AsParallel()
let attributes = t.GetCustomAttributes(typeof(HelpAttribute), true)
where attributes != null && attributes.Length > 0
select new { Type = t, Attributes = attributes.Cast<HelpAttribute>() };

如前所述,反思是一条出路。如果要经常调用这个函数,我强烈建议缓存结果,因为反射,特别是枚举每个类,可能会非常慢。

这是我的代码片段,它贯穿所有已加载程序集中的所有类型:

// this is making the assumption that all assemblies we need are already loaded.
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (Type type in assembly.GetTypes())
{
var attribs = type.GetCustomAttributes(typeof(MyCustomAttribute), false);
if (attribs != null && attribs.Length > 0)
{
// add to a cache.
}
}
}

其他答案参考 自定义属性。添加这个作为使用 定义的例子

Assembly assembly = ...
var typesWithHelpAttribute =
from type in assembly.GetTypes()
where type.IsDefined(typeof(HelpAttribute), false)
select type;

可移植.NET 限制的情况下,以下代码应该起作用:

    public static IEnumerable<TypeInfo> GetAtributedTypes( Assembly[] assemblies,
Type attributeType )
{
var typesAttributed =
from assembly in assemblies
from type in assembly.DefinedTypes
where type.IsDefined(attributeType, false)
select type;
return typesAttributed;
}

或者对于大量使用基于循环状态的 yield return的程序集:

    public static IEnumerable<TypeInfo> GetAtributedTypes( Assembly[] assemblies,
Type attributeType )
{
foreach (var assembly in assemblies)
{
foreach (var typeInfo in assembly.DefinedTypes)
{
if (typeInfo.IsDefined(attributeType, false))
{
yield return typeInfo;
}
}
}
}

这是在已接受的解决方案之上的性能增强。尽管所有的类都可能很慢,但是因为有太多的类,所以迭代很慢。有时您可以过滤出整个程序集,而不需要查看它的任何类型。

例如,如果您正在查找自己声明的属性,则不希望任何系统 DLL 包含具有该属性的任何类型。集会。属性是检查系统 DLL 的快速方法。当我在一个真正的程序上尝试这个时,我发现我可以跳过30,101个类型,而且我只需要检查1,983个类型。

过滤的另一种方法是使用 Assembly。参考程序集。如果您想要具有特定属性的类,并且该属性是在特定程序集中定义的,那么您可能只关心该程序集和引用它的其他程序集。在我的测试中,这比检查 GlobalAssemblyCache 属性更有帮助。

我把这两种方法结合起来,得到的速度更快。

        string definedIn = typeof(XmlDecoderAttribute).Assembly.GetName().Name;
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
// Note that we have to call GetName().Name.  Just GetName() will not work.  The following
// if statement never ran when I tried to compare the results of GetName().
if ((!assembly.GlobalAssemblyCache) && ((assembly.GetName().Name == definedIn) || assembly.GetReferencedAssemblies().Any(a => a.Name == definedIn)))
foreach (Type type in assembly.GetTypes())
if (type.GetCustomAttributes(typeof(XmlDecoderAttribute), true).Length > 0)

我们可以改进安德鲁的答案,并将整个事情转换成一个 LINQ 查询。

    public static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly)
{
return assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0);
}

这是 Trade-Ideas Philip 提供的代码的另一个版本, 我已经将代码压缩到 linq 中,并将其插入到一个很好的静态函数中,您可以将其放在项目中。

原文: Https://stackoverflow.com/a/41411243/4122889

我还增加了 AsParallel()-在我的机器上有足够的核心等,并与’正常’大小的项目(这是完全主观的) ,这是最快的/

在没有 AsParallel()的情况下,大约200个结果需要1.5秒的时间,有了 AsParallel(),大约需要几毫秒——因此在我看来,这似乎是最快的。

注意,这会跳过 GAC 中的程序集。

private static IEnumerable<IEnumerable<T>> GetAllAttributesInAppDomain<T>()
{
var definedIn = typeof(T).Assembly.GetName().Name;
var assemblies = AppDomain.CurrentDomain.GetAssemblies();


var res = assemblies.AsParallel()
.Where(assembly => (!assembly.GlobalAssemblyCache) && ((assembly.GetName().Name == definedIn) ||
assembly.GetReferencedAssemblies()
.Any(a => a.Name == definedIn))
)
.SelectMany(c => c.GetTypes())
.Select(type => type.GetCustomAttributes(typeof(T), true)
.Cast<T>()
)
.Where(c => c.Any());


return res;
}


用法:

var allAttributesInAppDomain = GetAllAttributesInAppDomain<ExportViewAttribute>();

注意,如果每个类只有1个属性,所以不是多个属性,那么将结果从 IEnumerable<IEnumerable<T>>平铺到 IEnumerable<T>会更容易,如下所示:

var allAttributesInAppDomainFlattened = allAttributesInAppDomain.SelectMany(c => c);

请记住,这将使用 IEnumerable,因此调用 ToList()来实际运行函数。