リフレクションを使用して宣言順にプロパティを取得する


81

クラスで宣言されている順序でリフレクションを使用してすべてのプロパティを取得する必要があります。MSDNによると、使用時に順序を保証することはできませんGetProperties()

GetPropertiesメソッドは、アルファベット順や宣言順など、特定の順序でプロパティを返しません。

ただし、プロパティをで並べ替えることで回避策があることを読みましたMetadataToken。だから私の質問は、それは安全ですか?それに関するMSDNの情報が見つからないようです。または、この問題を解決する他の方法はありますか?

私の現在の実装は次のようになります。

var props = typeof(T)
   .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
   .OrderBy(x => x.MetadataToken);

12
とにかく、それは悪い考えです。注文値またはその他のメタデータを使用して独自の属性を作成し、その属性を使用してクラスのフィールドにマークを付けます。
Kirill Polishchuk 2012年

1
おそらく、注文のintを含む新しい属性を追加することができます。次に、プロパティを取得し、各プロパティのDisplayOrderAttributeを取得して、それで並べ替えますか?
blueChippy 2012年

1
好奇心から、なぜあなたはこれをしているのですか、あなたは何を達成しようとしていますか?
Sam Greenhalgh 2012年

6
@Magnusそれでも、フレームワーク自体の一部がこれに大きく依存しているため、この質問は依然として興味深いものです。たとえば、Serializable属性を使用したシリアル化では、メンバーは定義された順序で格納されます。少なくとも、ワーグナーは彼の著書「Effective C#」でこれを述べています
Pavel Voronin

回答:


145

.net 4.5 (およびvs2012の.net 4.0)では、[CallerLineNumber]属性を使用した巧妙なトリックを使用してリフレクションを使用すると、コンパイラーがプロパティに順序を挿入できるようになります。

[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
public sealed class OrderAttribute : Attribute
{
    private readonly int order_;
    public OrderAttribute([CallerLineNumber]int order = 0)
    {
        order_ = order;
    }

    public int Order { get { return order_; } }
}


public class Test
{
    //This sets order_ field to current line number
    [Order]
    public int Property2 { get; set; }

    //This sets order_ field to current line number
    [Order]
    public int Property1 { get; set; }
}

そして、リフレクションを使用します。

var properties = from property in typeof(Test).GetProperties()
                 where Attribute.IsDefined(property, typeof(OrderAttribute))
                 orderby ((OrderAttribute)property
                           .GetCustomAttributes(typeof(OrderAttribute), false)
                           .Single()).Order
                 select property;

foreach (var property in properties)
{
   //
}

部分クラスを処理する必要がある場合は、を使用してプロパティを追加で並べ替えることができます[CallerFilePath]


2
これは確かに非常に賢いです!それに対して反論があるのだろうか?実際、私にはかなりエレガントに見えます。私はLinq2CSVをCsvColumnAttribute使用していますが、これを継承してデフォルトFieldIndex値として使用すると思います
julealgon 2013

2
@julealgonこれに反対する議論は、属性がこのように使用されていることを理解していない場合、リファクタリング時に誰かが壊す可能性のある、文書化されていない位置APIがあるということだと思います。誰かがこれをコピーして貼り付け、次の人にコメントを残す動機を探している場合に備えて、それはまだかなりエレガントだと思います。
Joel B

2
これは、1つのクラスが別のクラスを継承し、すべてのプロパティの順序が必要な場合を除いて機能します。そのためのトリックもありますか?
ポール・バクスター

クラスが部分的に宣言されている場合、これは壊れると思います。継承とは異なり、APIはそれを理解できません。
Wu Zhenwei 2017年

1
非常に巧妙なアプローチですが、[Order]属性の設定を忘れると、null参照例外がスローされます。呼び出す前にnullを確認することをお勧めします。例:var properties = from property in typeof(Test).GetProperties()let orderAttribute =(property.GetCustomAttributes(typeof(OrderAttribute)、false).SingleOrDefault()== null?new OrderAttribute(0):property.GetCustomAttributes(typeof (OrderAttribute)、false).SingleOrDefault())as OrderAttribute orderby orderAttribute.Orderselectプロパティ;
Sopan Maiti 2017

15

属性ルートを使用する場合は、これが過去に使用した方法です。

public static IOrderedEnumerable<PropertyInfo> GetSortedProperties<T>()
{
  return typeof(T)
    .GetProperties()
    .OrderBy(p => ((Order)p.GetCustomAttributes(typeof(Order), false)[0]).Order);
}

次に、このように使用します。

var test = new TestRecord { A = 1, B = 2, C = 3 };

foreach (var prop in GetSortedProperties<TestRecord>())
{
    Console.WriteLine(prop.GetValue(test, null));
}

どこ;

class TestRecord
{
    [Order(1)]
    public int A { get; set; }

    [Order(2)]
    public int B { get; set; }

    [Order(3)]
    public int C { get; set; }
}

このメソッドは、すべてのプロパティで同等の属性を持たない型で実行すると明らかにバーフになるため、使用方法に注意してください。要件を満たすには十分です。

YahiaのMarcGravellの投稿へのリンクに良いサンプルがあるので、Order:Attributeの定義を省略しました。


2
これが表示要件である場合は、Orderフィールドを持つDataAnnotations.DisplayAttributeを使用するのが適切です。
ジャーフ2013

12

MSDNに よると、MetadataToken1つのモジュール内で一意であり、順序を保証するということは何もありません。

たとえそれがあなたが望むように振る舞ったとしても、それは実装固有であり、予告なしにいつでも変更される可能性があります。

この古いMSDNブログエントリを参照してください。

このような実装の詳細に依存しないようにすることを強くお勧めします。MarcGravellのこの回答を参照してください。

コンパイル時に何かが必要な場合は、Roslynを見ることができます(非常に初期の段階ですが)。


5

私がMetadataTokenによるソートをテストしたものは機能します。

ここのユーザーの何人かは、これはどういうわけか良いアプローチではない/信頼できないと主張していますが、私はまだその証拠を見ていません-おそらく、与えられたアプローチが機能しないときにここにコードスニペットを投稿できますか?

下位互換性について-現在.net4 / .net4.5で作業している間-Microsoftは.net5以降を作成しているため、この並べ替え方法は将来壊れることはないとほぼ考えられます。

もちろん、2017年に.net9にアップグレードすると、互換性が失われる可能性がありますが、その時点で、Microsoftの担当者はおそらく「公式の並べ替えメカニズム」を理解するでしょう。戻ったり、物事を壊したりするのは意味がありません。

プロパティの順序付けのために追加の属性を操作するのにも時間と実装が必要です-MetadataTokenの並べ替えが機能するかどうかわざわざするのはなぜですか?


1
2019年であり、.net-4.9もまだリリースされていません:-p。
binki

.netがメジャーバージョンをリリースするたびに、MetadataTokenやからのリターンの順序などを変更する絶好の機会ですGetProperties()。順序付けに依存できる理由についてのあなたの議論は、この動作を変更しない.netの将来のバージョンに依存できない理由についての議論とまったく同じです。すべての新しいリリースは実装の詳細を自由に変更できます。現在、.netは実際には「バグは機能である」という哲学に従っているため、実際には.netの順序が変更されることはおそらくないでしょうGetProperties()。APIが許可されていると言っているだけです。
binki

1

カスタム属性の代わりに、System.Component.DataAnnotationsでDisplayAttributeを使用できます。あなたの要件はとにかくディスプレイで何かをしなければなりません。


0

追加の依存関係に満足している場合は、Marc GravellのProtobuf-Netを使用して、リフレクションやキャッシングなどを実装する最良の方法を心配することなくこれを行うことができます。を使用してフィールドを装飾し、[ProtoMember]次を使用して番号順にフィールドにアクセスします。

MetaType metaData = ProtoBuf.Meta.RuntimeTypeModel.Default[typeof(YourTypeName)];

metaData.GetFields();

0

私はそれをこのようにしました:

 internal static IEnumerable<Tuple<int,Type>> TypeHierarchy(this Type type)
    {
        var ct = type;
        var cl = 0;
        while (ct != null)
        {
            yield return new Tuple<int, Type>(cl,ct);
            ct = ct.BaseType;
            cl++;
        }
    }

    internal class PropertyInfoComparer : EqualityComparer<PropertyInfo>
    {
        public override bool Equals(PropertyInfo x, PropertyInfo y)
        {
            var equals= x.Name.Equals(y.Name);
            return equals;
        }

        public override int GetHashCode(PropertyInfo obj)
        {
            return obj.Name.GetHashCode();
        }
    }

    internal static IEnumerable<PropertyInfo> GetRLPMembers(this Type type)
    {

        return type
            .TypeHierarchy()
            .SelectMany(t =>
                t.Item2
                .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                .Where(prop => Attribute.IsDefined(prop, typeof(RLPAttribute)))
                .Select(
                    pi=>new Tuple<int,PropertyInfo>(t.Item1,pi)
                )
             )
            .OrderByDescending(t => t.Item1)
            .ThenBy(t => t.Item2.GetCustomAttribute<RLPAttribute>().Order)
            .Select(p=>p.Item2)
            .Distinct(new PropertyInfoComparer());




    }

次のように宣言されたプロパティを使用します。

  [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class RLPAttribute : Attribute
{
    private readonly int order_;
    public RLPAttribute([CallerLineNumber]int order = 0)
    {
        order_ = order;
    }

    public int Order { get { return order_; } }

}

これは受け入れられた答えとどのように違うか、または優れていますか?
イアン・ケンプ

0

上記の受け入れられたソリューションに基づいて、正確なインデックスを取得するには、次のようなものを使用できます

与えられた

public class MyClass
{
   [Order] public string String1 { get; set; }
   [Order] public string String2 { get; set; }
   [Order] public string String3 { get; set; }
   [Order] public string String4 { get; set; }   
}

拡張機能

public static class Extensions
{

   public static int GetOrder<T,TProp>(this T Class, Expression<Func<T,TProp>> propertySelector)
   {
      var body = (MemberExpression)propertySelector.Body;
      var propertyInfo = (PropertyInfo)body.Member;
      return propertyInfo.Order<T>();
   }

   public static int Order<T>(this PropertyInfo propertyInfo)
   {
      return typeof(T).GetProperties()
                      .Where(property => Attribute.IsDefined(property, typeof(OrderAttribute)))
                      .OrderBy(property => property.GetCustomAttributes<OrderAttribute>().Single().Order)
                      .ToList()
                      .IndexOf(propertyInfo);
   }
}

使用法

var myClass = new MyClass();
var index = myClass.GetOrder(c => c.String2);

、エラーチェックやフォールトトレランスはありません。味にコショウと塩を加えることができます


0

別の可能性は、System.ComponentModel.DataAnnotations.DisplayAttribute Orderプロパティを使用することです。組み込みであるため、新しい特定の属性を作成する必要はありません。

次に、このような順序付けられたプロパティを選択します

const int defaultOrder = 10000;
var properties = type.GetProperties().OrderBy(p => p.FirstAttribute<DisplayAttribute>()?.GetOrder() ?? defaultOrder).ToArray();

そして、クラスはこのように提示することができます

public class Toto {
    [Display(Name = "Identifier", Order = 2)
    public int Id { get; set; }

    [Display(Name = "Description", Order = 1)
    public string Label {get; set; }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.