167

MSDNの例に基づく質問。

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

4

8 に答える 8

223

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

static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly) {
    foreach(Type type in assembly.GetTypes()) {
        if (type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0) {
            yield return type;
        }
    }
}
于 2009-03-03T16:49:31.863 に答える
117

現在のアプリ ドメインに読み込まれているすべてのアセンブリのすべてのクラスを列挙する必要があります。これを行うには、現在のアプリ ドメインのインスタンスでGetAssembliesメソッドを呼び出します。AppDomain

そこから、GetExportedTypes(パブリック タイプのみが必要な場合) またはGetTypeson eachAssemblyを呼び出して、アセンブリに含まれるタイプを取得します。

次に、各インスタンスで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を使用して (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>() };
于 2009-03-03T16:53:32.760 に答える
39

他の回答はGetCustomAttributesを参照しています。これをIsDefinedの使用例として追加する

Assembly assembly = ...
var typesWithHelpAttribute = 
        from type in assembly.GetTypes()
        where type.IsDefined(typeof(HelpAttribute), false)
        select type;
于 2013-02-05T23:30:31.000 に答える
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.
        }
    }
}
于 2009-03-03T17:03:27.947 に答える
10

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

たとえば、自分で宣言した属性を探している場合、システム DLL のいずれにもその属性を持つ型が含まれているとは思われません。Assembly.GlobalAssemblyCache プロパティは、システム DLL をチェックする簡単な方法です。実際のプログラムでこれを試したところ、30,101 型をスキップでき、1,983 型だけをチェックする必要があることがわかりました。

フィルタリングするもう 1 つの方法は、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)
于 2016-12-31T19:40:57.923 に答える
4

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;
                }
            }
        }
    }
于 2016-07-27T09:54:19.850 に答える
1

これは、Trade-Ideas philip によって提供されたコードの別のバージョンです。コードを linq に圧縮し、プロジェクトにドロップできる素敵な静的関数にプラグインしました。

オリジナル: https://stackoverflow.com/a/41411243/4122889

また、追加AsParallel()しました-十分なコアなどを備えたマシンで、「通常の」サイズのプロジェクト(完全に主観的です)では、これが最速でした/

これがないAsParallel()と、約 200 の結果に 1.5 秒かかりました。これを使用すると、約数ミリ秒かかりました。したがって、これが最速のようです。

これにより、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<IEnumerable<T>>を平坦化する方が簡単であることに注意してください。IEnumerable<T>

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

IEnumerableこれはso 呼び出しToList()を使用して実際に関数を実行することを覚えておいてください。

于 2021-02-02T16:26:54.793 に答える