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


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(당신이 공공의 유형을 원하는 경우) 또는 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>() };

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.