【问题标题】:How enumerate all classes with custom class attribute?如何枚举具有自定义类属性的所有类?
【发布时间】:2019-04-08 23:16:49
【问题描述】:

基于MSDN example的问题。

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

【问题讨论】:

  • This 可能也有帮助。

标签: c# class attributes custom-attributes enumerate


【解决方案1】:

是的,当然。使用反射:

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

【讨论】:

  • 同意,但在这种情况下,我们可以按照 casperOne 的解决方案以声明方式进行。能使用yield真是太好了,不用就更好了:)
  • 我喜欢 LINQ。爱它,其实。但它依赖于 .NET 3.5,而 yield return 则没有。此外,LINQ 最终分解为与收益回报基本相同的东西。那么你得到了什么?一种特定的 C# 语法,这是一种偏好。
  • @AndrewArnott 最少和最短的代码行与性能无关,它们只是可读性和可维护性的可能贡献者。我质疑他们分配最少的对象并且性能会更快的说法(尤其是没有经验证明);您基本上已经编写了Select 扩展方法,并且编译器将生成一个状态机,就像您调用Select 一样,因为您使用了yield return。最后,在大多数情况下可能获得的任何性能提升都是微优化。
  • @casperOne,你提到我的方式创建了一个状态机。那个状态机我说它创建的IEnumerable。使用Select 意味着您也在分配委托和闭包,而我的方法不需要。
  • 完全正确,@casperOne。一个非常小的差异,尤其是与反射本身的权重相比。可能永远不会出现在性能跟踪中。
【解决方案2】:

好吧,您必须枚举加载到当前应用程序域的所有程序集中的所有类。为此,您可以在当前应用程序域的 AppDomain 实例上调用 GetAssemblies method

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

然后,您将在每个 Type 实例上调用 GetCustomAttributes extension method,并传递您希望查找的属性的类型。

您可以使用 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>() };

上述查询将为您获取应用了您的属性的每种类型,以及分配给它的属性的实例。

请注意,如果您将大量程序集加载到应用程序域中,则该操作可能会很昂贵。您可以使用Parallel 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>() };

【讨论】:

  • 枚举 all 加载的程序集中的所有类型只会非常慢,而且不会给你带来太多好处。这也是潜在的安全风险。您可能可以预测哪些程序集将包含您感兴趣的类型。只需枚举其中的类型即可。
  • @Andrew Arnott:正确,但这就是我们所要求的。修剪特定程序集的查询很容易。这还有一个额外的好处,那就是为您提供类型和属性之间的映射。
  • 您可以通过 System.Reflection.Assembly.GetExecutingAssembly() 在当前程序集上使用相同的代码
  • @ChrisMoschini 是的,您可以,但您可能并不总是希望扫描当前程序集。最好让它保持打开状态。
  • 我已经做过很多次了,但没有太多方法可以提高效率。您可以跳过 microsoft 程序集(它们使用相同的密钥签名,因此很容易避免使用 AssemblyName。您可以在静态中缓存结果,这是加载程序集的 AppDomain 所独有的(必须缓存完整的您检查的程序集的名称,以防其他人同时加载)。发现自己在这里,因为我正在研究缓存属性中属性类型的加载实例。不确定该模式,不确定它们何时被实例化等。
【解决方案3】:

如前所述,反思是要走的路。如果你要经常调用这个,我强烈建议缓存结果,因为反射,尤其是枚举每个类,可能会很慢。

这是我的代码的 sn-p,它运行在所有加载的程序集中的所有类型中:

// 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.
        }
    }
}

【讨论】:

    【解决方案4】:

    其他答案参考GetCustomAttributes。添加这个作为使用IsDefined的示例

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

    【讨论】:

    • 我相信它是使用框架预期方法的正确解决方案。
    【解决方案5】:

    如果是Portable .NET limitations,下面的代码应该可以工作:

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

    【讨论】:

      【解决方案6】:

      这是在公认解决方案之上的性能增强。迭代所有类可能会很慢,因为有很多。有时您可以过滤掉整个程序集而不查看它的任何类型。

      例如,如果您正在寻找您自己声明的属性,您不希望任何系统 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)
      

      【讨论】:

        【解决方案7】:

        我们可以改进 Andrew 的答案并将整个事情转换为一个 LINQ 查询。

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

        【讨论】:

          【解决方案8】:

          这是 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&lt;IEnumerable&lt;T&gt;&gt; 展平为 IEnumerable&lt;T&gt;,如下所示:

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

          记住,这使用IEnumerable 所以调用ToList() 来实际运行函数。

          【讨论】:

            猜你喜欢
            • 2017-04-02
            • 1970-01-01
            • 1970-01-01
            • 2011-04-03
            • 1970-01-01
            • 2019-03-20
            • 1970-01-01
            • 2017-06-04
            相关资源
            最近更新 更多