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

StackOverflow https://stackoverflow.com/questions/607178

質問

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 < code> Assembly を使用して、アセンブリに含まれる型を取得します。

次に、 GetCustomAttributes Type メソッド a>インスタンス、検索する属性のタイプを渡します。

これを簡単にするために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>() };

その他の回答は、 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)

ポータブル.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;
                }
            }
        }
    }

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

    public static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly)
    {
        return assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0);
    }
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top