GetProperties()は、インターフェース継承階層のすべてのプロパティを返します


96

次の架空の継承階層を想定します。

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
純粋な輝き!これで私がオペレーションの質問に似ていた問題を解決してくれてありがとう。
kamui

1
BindingFlags.FlattenHierarchyへの参照は、BindingFlags.Instanceも使用しているため、冗長です。
Chris Ward

1
私はこれを実装しましたが、のStack<Type>代わりにQueue<>。スタックでは、祖先は、このような順序を維持しているinterface IFoo : IBar, IBazところIBar : IBubbleと「IBaz:IFlubber , the order of reflection becomes: IBAR , IBubble , IBaz , IFlubber , IFoo`。
IAbstract 2014年

4
GetInterfaces()は型によって実装されたすべてのインターフェースをすでに返しているので、再帰やキューは必要ありません。マークが指摘したように、階層はないので、なぜ何かを「再帰」する必要があるのでしょうか。
2015

3
@FrankyHollywoodそれがあなたが使用しない理由ですGetPropertiesGetInterfacesすべてのインターフェースのフラット化されたリストを返す開始タイプで使用し、GetProperties各インターフェースで単純に実行します。再帰の必要はありません。インターフェイスには継承または基本タイプはありません。
下り坂

77

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
これは間違いなく正しい答えです!不格好な再帰は必要ありません。
下り坂

しっかりお返事ありがとうございます。基本インターフェイスでプロパティの値を取得するにはどうすればよいですか?
ilker unal 2015年

1
@ilkerunal:通常の方法:GetValue取得したを呼び出しPropertyInfo、インスタンス(プロパティ値を取得する)をパラメーターとして渡します。例:var list = new[] { 'a', 'b', 'c' }; var count = typeof(IList).GetPublicProperties().First(i => i.Name == "Count").GetValue(list);←はCountICollectionではなくで定義されていても、3を返しIListます。
ダグラス

2
このソリューションには、同じ名前のプロパティを複数回返す可能性があるという欠点があります。プロパティリストを個別にするには、結果をさらに整理する必要があります。受け入れられた答えは、一意の名前でプロパティが返されることを保証し、継承チェーンで最も近いものを取得することでそうするため、より正しい解決策です。
user3524983 2017

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()が型に実装されたすべてのインターフェースをすでに返していることも認識できません。「階層」がないため、再帰やキューは必要ありません。
下り坂

3

まったく同じ問題に、ここで説明する回避策があります

FlattenHierarchyは機能しません。(静的変数のみ。インテリセンスでそう言う)

回避策。重複に注意してください。

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();
    }

次は投稿後ではなく投稿前にデバッグします:-)


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.