인터페이스 상속 계층에 대한 모든 속성을 반환하는 GetProperties ()


97

다음과 같은 가상의 상속 계층을 가정합니다.

public interface IA
{
  int ID { get; set; }
}

public interface IB : IA
{
  string Name { get; set; }
}

리플렉션을 사용하고 다음 호출을합니다.

typeof(IB).GetProperties(BindingFlags.Public | BindingFlags.Instance) 

IB" Name" 인 인터페이스의 속성 만 생성합니다 .

다음 코드에서 유사한 테스트를 수행하면

public abstract class A
{
  public int ID { get; set; }
}

public class B : A
{
  public string Name { get; set; }
}

이 호출 typeof(B).GetProperties(BindingFlags.Public | BindingFlags.Instance)PropertyInfo" ID"및 " Name"에 대한 개체 배열을 반환합니다 .

첫 번째 예제에서와 같이 인터페이스에 대한 상속 계층 구조의 모든 속성을 쉽게 찾을 수있는 방법이 있습니까?

답변:


112

@Marc Gravel의 예제 코드를 유용한 확장 메서드로 변경하여 클래스와 인터페이스를 모두 캡슐화했습니다. 또한 예상되는 동작이라고 생각하는 인터페이스 속성을 먼저 추가합니다.

public static PropertyInfo[] GetPublicProperties(this Type type)
{
    if (type.IsInterface)
    {
        var propertyInfos = new List<PropertyInfo>();

        var considered = new List<Type>();
        var queue = new Queue<Type>();
        considered.Add(type);
        queue.Enqueue(type);
        while (queue.Count > 0)
        {
            var subType = queue.Dequeue();
            foreach (var subInterface in subType.GetInterfaces())
            {
                if (considered.Contains(subInterface)) continue;

                considered.Add(subInterface);
                queue.Enqueue(subInterface);
            }

            var typeProperties = subType.GetProperties(
                BindingFlags.FlattenHierarchy 
                | BindingFlags.Public 
                | BindingFlags.Instance);

            var newPropertyInfos = typeProperties
                .Where(x => !propertyInfos.Contains(x));

            propertyInfos.InsertRange(0, newPropertyInfos);
        }

        return propertyInfos.ToArray();
    }

    return type.GetProperties(BindingFlags.FlattenHierarchy
        | BindingFlags.Public | BindingFlags.Instance);
}

2
순수한 광채! 내가 op의 질문과 비슷한 문제를 해결해 주셔서 감사합니다.
kamui 2012

1
BindingFlags.FlattenHierarchy에 대한 참조는 BindingFlags.Instance도 사용하므로 중복됩니다.
크리스 워드

1
나는 그러나이 구현 한 Stack<Type>대신의 Queue<>. 스택으로 조상은 순서를 유지하는 interface IFoo : IBar, IBazIBar : IBubble과 'IBaz : IFlubber , the order of reflection becomes: IBar , IBubble , IBaz , IFlubber , IFoo`.
IAbstract 2014 년

4
GetInterfaces ()는 이미 형식에 의해 구현 된 모든 인터페이스를 반환하므로 재귀 나 큐가 필요하지 않습니다. Marc가 지적했듯이 계층 구조가 없는데 왜 우리가 "반복"해야합니까?
glopes

3
@FrankyHollywood 그래서 GetProperties. GetInterfaces모든 인터페이스의 평면화 된 목록을 반환하고 GetProperties각 인터페이스에서 간단히 수행하는 시작 유형에서 사용 합니다 . 재귀가 필요 없습니다. 인터페이스에는 상속 또는 기본 유형이 없습니다.
glopes

78

Type.GetInterfaces 평면화 된 계층을 반환하므로 재귀 하강이 필요하지 않습니다.

LINQ를 사용하여 전체 메서드를 훨씬 더 간결하게 작성할 수 있습니다.

public static IEnumerable<PropertyInfo> GetPublicProperties(this Type type)
{
    if (!type.IsInterface)
        return type.GetProperties();

    return (new Type[] { type })
           .Concat(type.GetInterfaces())
           .SelectMany(i => i.GetProperties());
}

8
이것은 확실히 정답이어야합니다! 투박한 재귀가 필요하지 않습니다.
glopes

확실한 답변 감사합니다. 기본 인터페이스에서 속성 값을 어떻게 얻을 수 있습니까?
ilker unal

1
@ilkerunal : 일반적인 방법 : GetValue검색된을 호출 하고 PropertyInfo인스턴스 (속성 값을 가져올)를 매개 변수로 전달합니다. 예 : var list = new[] { 'a', 'b', 'c' }; var count = typeof(IList).GetPublicProperties().First(i => i.Name == "Count").GetValue(list);← 비록, 3을 반환 Count내에 정의 ICollection하지 IList.
Douglas

2
이 솔루션은 동일한 이름의 속성을 여러 번 반환 할 수 있다는 단점이 있습니다. 고유 한 속성 목록에 대한 결과를 추가로 정리해야합니다. 허용되는 대답은 고유 한 이름을 가진 속성을 반환하고 상속 체인에서 가장 가까운 속성을 잡아서 반환하므로 더 정확한 솔루션입니다.
user3524983

1
@AntWaters이 GetInterfaces (가) 경우에 필요하지 않은 type클래스는 콘크리트 클래스가 있기 때문에, 반드시 구현하는 모든 상속 체인 아래 모든 인터페이스에 정의 된 속성을. GetInterfaces이 시나리오에서 사용하면 모든 속성이 복제됩니다.
Chris Schaller

15

인터페이스 계층은 고통 스럽습니다. (더 나은 용어를 원하는 경우) 여러 "부모"를 가질 수 있기 때문에 실제로 "상속"되지 않습니다.

"편 평화"(다시 말하지만 올바른 용어는 아님) 계층 구조에는 인터페이스가 구현하고 거기에서 작동하는 모든 인터페이스를 검사하는 것이 포함될 수 있습니다.

interface ILow { void Low();}
interface IFoo : ILow { void Foo();}
interface IBar { void Bar();}
interface ITest : IFoo, IBar { void Test();}

static class Program
{
    static void Main()
    {
        List<Type> considered = new List<Type>();
        Queue<Type> queue = new Queue<Type>();
        considered.Add(typeof(ITest));
        queue.Enqueue(typeof(ITest));
        while (queue.Count > 0)
        {
            Type type = queue.Dequeue();
            Console.WriteLine("Considering " + type.Name);
            foreach (Type tmp in type.GetInterfaces())
            {
                if (!considered.Contains(tmp))
                {
                    considered.Add(tmp);
                    queue.Enqueue(tmp);
                }
            }
            foreach (var member in type.GetMembers())
            {
                Console.WriteLine(member.Name);
            }
        }
    }
}

7
동의하지 않습니다. Marc에 대한 모든 존경심으로,이 답변은 GetInterfaces ()가 이미 유형에 대해 구현 된 모든 인터페이스를 반환한다는 것을 깨닫지 못합니다. "계층 구조"가 없기 때문에 재귀 나 큐가 필요하지 않습니다.
glopes

3

정확히 동일한 문제에 여기에 설명 된 해결 방법이 있습니다 .

FlattenHierarchy가 btw 작동하지 않습니다. (정적 변수에서만. intellisense에서 그렇게 말합니다)

해결 방법. 중복에주의하십시오.

PropertyInfo[] pis = typeof(IB).GetProperties(BindingFlags.Public | BindingFlags.Instance);
Type[] tt = typeof(IB).GetInterfaces();
PropertyInfo[] pis2 = tt[0].GetProperties(BindingFlags.Public | BindingFlags.Instance);

2

@douglas 및 @ user3524983에 대한 응답으로 다음이 OP의 질문에 답해야합니다.

    static public IEnumerable<PropertyInfo> GetPropertiesAndInterfaceProperties(this Type type, BindingFlags bindingAttr = BindingFlags.Public | BindingFlags.Instance)
    {
        if (!type.IsInterface) {
            return type.GetProperties( bindingAttr);
        }

        return type.GetInterfaces().Union(new Type[] { type }).SelectMany(i => i.GetProperties(bindingAttr)).Distinct();
    }

또는 개별 속성의 경우 :

    static public PropertyInfo GetPropertyOrInterfaceProperty(this Type type, string propertyName, BindingFlags bindingAttr = BindingFlags.Public|BindingFlags.Instance)
    {
        if (!type.IsInterface) {
            return type.GetProperty(propertyName, bindingAttr);
        }

        return type.GetInterfaces().Union(new Type[] { type }).Select(i => i.GetProperty( propertyName, bindingAttr)).Distinct().Where(propertyInfo => propertyInfo != null).Single();
    }

OK 다음에 게시하기 전에 디버깅하겠습니다 :-)


1

이것은 사용자 정의 MVC 모델 바인더에서 멋지고 간결하게 작동했습니다. 그러나 모든 반사 시나리오로 추정 할 수 있어야합니다. 아직도 너무 지나가는 냄새

    var props =  bindingContext.ModelType.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance).ToList();

    bindingContext.ModelType.GetInterfaces()
                      .ToList()
                      .ForEach(i => props.AddRange(i.GetProperties()));

    foreach (var property in props)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.