2009-03-03 130 views
119

問題根據MSDN example如何枚舉具有自定義類屬性的所有類?

假設我們在獨立桌面應用程序中有一些C#類和HelpAttribute。有沒有可能枚舉具有這種屬性的所有類?以這種方式識別班級有意義嗎?自定義屬性將用於列出可能的菜單選項,選擇項目將帶來此類的實例。類/項目的數量將會增長緩慢,但這樣我們可以避免在其他地方列舉它們,我想。

+1

MSDN示例鏈接是一個死鏈接。 – MadTigger 2017-10-11 00:03:58

回答

159

是的,絕對。使用反射:

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

同意,但在這種情況下,我們可以按照casperOne的解決方案以聲明方式進行。很高興能夠使用良率,更好的是不必:) – 2009-03-03 17:23:31

+8

我喜歡LINQ。其實,愛它。但是它需要依賴於.NET 3.5,而不是返回。此外,LINQ最終分解成與收益回報基本相同的東西。那麼你有什麼收穫?特定的C#語法,這是一個首選項。 – 2009-03-03 18:14:55

+0

因此,使用yield來代替編寫自己的枚舉器...... – 2014-03-19 07:05:53

80

那麼,你將不得不枚舉通過加載到當前應用程序域的所有程序集中的所有類。爲此,您可以在當前應用程序域的AppDomain實例上調用GetAssemblies method

從那裏,你可以撥打GetExportedTypes(如果你只是想要公共類型)或GetTypes在每個Assembly獲得包含在程序集中的類型。

然後,您將調用每個Type實例上的GetCustomAttributes 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,以減少操作的時間,像這樣:

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

如前所述,反思是要走的路。如果你要頻繁地調用它,我強烈建議緩存結果,因爲反思,特別是通過每個類的枚舉,可能會非常緩慢。

這是我的代碼片段,通過在所有加載的程序集的所有類型運行:

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

其他答案參考GetCustomAttributes。添加這一個如使用IsDefined

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

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

這是在公認的解決方案之上的性能增強。迭代雖然所有類可能會很慢,因爲有這麼多。有時你可以在不查看任何類型的情況下過濾出整個程序集。

例如,如果您正在查找自己聲明的屬性,則不會指望任何系統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)