基于MSDN 示例的问题。
假设我们在独立桌面应用程序中有一些带有 HelpAttribute 的 C# 类。是否可以枚举具有此类属性的所有类?以这种方式识别类有意义吗?自定义属性将用于列出可能的菜单选项,选择项目将显示此类的屏幕实例。类/项目的数量将缓慢增长,但我认为这样我们可以避免在其他地方枚举它们。
基于MSDN 示例的问题。
假设我们在独立桌面应用程序中有一些带有 HelpAttribute 的 C# 类。是否可以枚举具有此类属性的所有类?以这种方式识别类有意义吗?自定义属性将用于列出可能的菜单选项,选择项目将显示此类的屏幕实例。类/项目的数量将缓慢增长,但我认为这样我们可以避免在其他地方枚举它们。
是的,一点没错。使用反射:
static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly) {
foreach(Type type in assembly.GetTypes()) {
if (type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0) {
yield return type;
}
}
}
好吧,您必须枚举加载到当前应用程序域的所有程序集中的所有类。为此,您将在当前应用程序域的实例上调用该GetAssemblies
方法。AppDomain
从那里,您将调用GetExportedTypes
(如果您只想要公共类型)或GetTypes
在每个上调Assembly
用以获取程序集中包含的类型。
然后,您将在每个实例上调用GetCustomAttributes
扩展方法Type
,并传递您希望查找的属性的类型。
您可以使用 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>() };
上面的查询将为您提供应用了您的属性的每种类型,以及分配给它的属性的实例。
请注意,如果您将大量程序集加载到应用程序域中,则该操作可能会很昂贵。您可以使用并行 LINQ来减少操作时间(以 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>() };
如果程序集中有大量类型,那么您可以再次使用 Parallel 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>() };
其他答案参考GetCustomAttributes。添加这个作为使用IsDefined的示例
Assembly assembly = ...
var typesWithHelpAttribute =
from type in assembly.GetTypes()
where type.IsDefined(typeof(HelpAttribute), false)
select type;
如前所述,反思是要走的路。如果你要经常调用它,我强烈建议缓存结果,因为反射,尤其是枚举每个类,可能会很慢。
这是我的代码片段,它贯穿所有加载的程序集中的所有类型:
// 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.
}
}
}
这是在公认解决方案之上的性能增强。迭代所有类可能会很慢,因为有很多。有时您可以过滤掉整个程序集,而无需查看其任何类型。
例如,如果您正在寻找您自己声明的属性,您不希望任何系统 DLL 包含具有该属性的任何类型。Assembly.GlobalAssemblyCache 属性是检查系统 DLL 的快速方法。当我在一个真实的程序上尝试这个时,我发现我可以跳过 30,101 种类型,而我只需要检查 1,983 种类型。
另一种过滤方法是使用 Assembly.ReferencedAssemblies。大概如果您想要具有特定属性的类,并且该属性是在特定程序集中定义的,那么您只关心该程序集和引用它的其他程序集。在我的测试中,这比检查 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)
在Portable .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;
}
}
}
}
这是 Trade-Ideas philip 提供的代码的另一个版本,我已将代码压缩为 linq,将其插入到一个不错的静态函数中,您可以将其放入项目中。
原文: https ://stackoverflow.com/a/41411243/4122889
我还添加了AsParallel()
- 在我的机器上有足够的内核等,并且有一个“正常”大小的项目(这完全是主观的),这是最快的/
没有AsParallel()
这个需要 1,5 秒来获得大约 200 个结果,而有了它,它需要大约几毫秒 - 因此这对我来说似乎是最快的。
请注意,这会跳过 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
so 调用ToList()
来实际运行该函数。
我们可以改进 Andrew 的答案并将整个事情转换为一个 LINQ 查询。
public static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly)
{
return assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0);
}