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


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 # 구문, 즉 기본 설정입니다.
— 앤드류 아 노트

1
@AndrewArnott Fewest와 최단 코드 라인은 성능과 관련이 없으며 가독성과 유지 보수성에 기여할 수 있습니다. 나는 그들이 가장 적은 수의 객체를 할당하고 성능이 더 빠를 것이라는 진술에 도전한다 (특히 경험적 증거가 없음). 기본적으로 Select확장 메소드를 작성했으며 컴파일러는 Select의 사용으로 인해 호출했을 때와 마찬가지로 상태 머신을 생성합니다 yield return. 마지막으로, 대부분의 경우에 얻을 수 있는 성능 향상은 미세 최적화입니다.
— casperOne

1
맞습니다, @casperOne. 반사 자체의 무게와 비교할 때 매우 작은 차이. 아마 성능 추적에서 나타나지 않을 것입니다.
— Andrew Arnott

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 ()가 바로 현재의 조립에 같은 코드를 사용할 수 있습니다
— 크리스 Moschini

@ChrisMoschini 예, 가능하지만 항상 현재 어셈블리를 스캔하지 않을 수도 있습니다. 열어 두는 것이 좋습니다.
— casperOne

나는 이것을 여러 번 해왔고 그것을 효율적으로 만드는 방법은 많지 않습니다. Microsoft 어셈블리를 건너 뛸 수 있습니다 (동일한 키로 서명되었으므로 AssemblyName 사용을 피하는 것이 매우 쉽습니다). 어셈블리가로드 된 AppDomain에 고유 한 정적 결과를 캐시에 캐시 할 수 있습니다 (전체를 캐시해야 함) 그 동안 다른 모듈이로드 된 경우 확인한 어셈블리 이름) 속성 내에서로드 된 속성 유형의 인스턴스 캐싱을 조사 할 때 여기에서 나 자신을 발견했습니다. 패턴이 확실하지 않고 인스턴스화시기를 잘 모르는 경우 등

34

다른 답변은 GetCustomAttributes를 참조 합니다. 이 정의를 IsDefined 사용의 예로 추가

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

3
프레임 워크 의도 된 방법을 사용하는 것이 올바른 솔루션이라고 생각합니다.
— Alexey Omelchenko

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의 답변을 개선하고 모든 것을 하나의 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.