[C#] 커스텀 클래스 속성을 가진 모든 클래스를 어떻게 열거합니까?

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(당신이 공공의 유형을 원하는 경우) 또는 GetTypesAssembly어셈블리에 포함 된 유형을 얻을 수 있습니다.

그런 다음 각 인스턴스 에서 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>() };


답변

다른 답변은 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의 답변을 개선하고 모든 것을 하나의 LINQ 쿼리로 변환 할 수 있습니다.

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