web-dev-qa-db-ja.com

カスタムクラス属性を持つすべてのクラスを列挙するにはどうすればよいですか?

MSDNの例 に基づく質問。

スタンドアロンのデスクトップアプリケーションにHelpAttributeを持つC#クラスがあるとしましょう。そのような属性を持つすべてのクラスを列挙することは可能ですか?このようにクラスを認識することは理にかなっていますか?カスタム属性を使用して、可能なメニューオプションをリストします。項目を選択すると、そのようなクラスのインスタンスが画面に表示されます。クラス/アイテムの数はゆっくりと増加しますが、この方法では、他の場所でそれらをすべて列挙することを避けることができると思います。

141
tomash

そのとおり。リフレクションを使用する:

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

さて、現在のアプリドメインに読み込まれるすべてのアセンブリのすべてのクラスを列挙する必要があります。これを行うには、現在のアプリドメインの 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>() };

上記のクエリは、属性が適用された各タイプと、それに割り当てられた属性のインスタンスを取得します。

アプリケーションドメインに多数のアセンブリが読み込まれている場合、その操作は高価になる可能性があることに注意してください。 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>() };
97
casperOne

その他の回答のリファレンス GetCustomAttributesIsDefined の使用例としてこれを追加する

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

すでに述べたように、リフレクションが進むべき道です。これを頻繁に呼び出す場合、リフレクション、特にすべてのクラスの列挙が非常に遅くなる可能性があるため、結果をキャッシュすることを強くお勧めします。

これは、ロードされたすべてのアセンブリのすべてのタイプを実行するコードのスニペットです。

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

これは、受け入れられているソリューションに加えてパフォーマンスが強化されています。非常に多くのクラスがあるため、すべてのクラスが低速になる可能性があります。場合によっては、その種類を見ずにアセンブリ全体を除外できます。

たとえば、自分で宣言した属性を探している場合、その属性を持つ型がシステム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

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;
                }
            }
        }
    }
3
Lorenz Lo Sauer

Andrewの答えを改善し、全体を1つのLINQクエリに変換できます。

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