カスタムクラス属性を持つすべてのクラスをどのように列挙しますか?


151

MSDNの例に基づく質問。

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

回答:


205

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

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

7
同意しますが、この場合、casperOneのソリューションに従って宣言的に行うことができます。利回りを使用できるのは素晴らしいことです。必要がないことはさらに良いことです:)
Jon Skeet

9
LINQが好きです。実際にそれを愛しなさい。しかし、それは.NET 3.5への依存を必要とします。また、LINQは最終的に、実質的にイールドリターンと同じものに分解されます。それであなたは何を得ましたか?特定のC#構文、つまり設定です。
Andrew Arnott

1
@AndrewArnott最短で最短のコード行はパフォーマンスとは無関係であり、可読性と保守性に貢献する可能性があるだけです。私は、オブジェクトの割り当てが最も少なく、パフォーマンスがより速くなる(特に、経験的な証明がない場合)という声明に異議を唱えます。基本的にSelect拡張メソッドを記述しました。コンパイラーはSelect、を使用しているために呼び出した場合と同じように、ステートマシンを生成しますyield return。最後に、ほとんどの場合に得られる可能性のあるパフォーマンスの向上は、マイクロ最適化です。
casperOne 2014年

1
@casperOne。特に反射自体の重みと比較すると、非常に小さな違いです。おそらく、perfトレースで表示されることはありません。
Andrew Arnott 2014

1
もちろん、Resharperは次のような「foreachループをLINQ式に変換できる」と言っています。assembly.GetTypes()。Where(type => type.GetCustomAttributes(typeof(HelpAttribute)、true).Length> 0);
David Barrows

107

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

1
ロードされたすべてのアセンブリのすべてのタイプを列挙するのは非常に遅く、あまり効果がありません。また、セキュリティリスクの可能性もあります。おそらく、目的の型が含まれるアセンブリを予測できます。型を列挙するだけです。
Andrew Arnott

@Andrew Arnott:正解ですが、これは要求されたものです。特定のアセンブリのクエリを削除するのは簡単です。これには、タイプと属性の間のマッピングを提供するという追加の利点もあります。
casperOne

1
System.Reflection.Assembly.GetExecutingAssembly()を使用して、現在のアセンブリのみで同じコードを使用できます
Chris Moschini

@ChrisMoschiniはい、できますが、常に現在のアセンブリをスキャンする必要はありません。開いたままにしておくとよいでしょう。
casperOne 2012年

私はこれを何度も行ってきましたが、効率的にする方法は多くありません。Microsoftアセンブリをスキップできます(それらは同じキーで署名されているため、AssemblyNameの使用を避けるのは非常に簡単です。アセンブリは、読み込まれるAppDomainに固有のstatic内に結果をキャッシュできます(完全なキャッシュが必要です)他のものがロードされる場合に備えてチェックしたアセンブリの名前。属性内の属性タイプのロードされたインスタンスのキャッシュを調査しているため、ここで自分自身を見つけました。そのパターンがわからない、インスタンス化のタイミングがわからない、など。


11

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

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

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

9

これは、認められたソリューションに加えてパフォーマンスを向上させるものです。非常に多くのクラスがあるため、すべてのクラスが低速になる可能性があります。場合によっては、アセンブリのタイプを確認せずに、アセンブリ全体を除外することができます。

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

4

以下の場合、ポータブル.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;
                }
            }
        }
    }

0

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

    public static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly)
    {
        return assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0);
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.