web-dev-qa-db-ja.com

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

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

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

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

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

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

.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]を使用してプロパティをさらにソートできます。

129
ghord

[〜#〜] msdn [〜#〜]MetadataTokenによると、1つのモジュール内で一意です。順序がまったく保証されているということはありません。

希望どおりに動作する場合でも、実装固有であり、予告なくいつでも変更される可能性があります。

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

このような実装の詳細への依存関係から離れることを強くお勧めします- Marc Gravellからのこの回答 を参照してください。

コンパイル時に何かが必要な場合は、 Roslyn をご覧ください(ただし、非常に早い段階です)。

12
Yahia

属性ルートを使用する場合、これまでに使用した方法を次に示します。

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

すべてのプロパティで同等の属性を持たないタイプでメソッドを実行すると、メソッドはbarfするので、使用方法に注意し、要件を満たすには十分であることは明らかです。

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

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

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

後方互換性について-.net 4/.net 4.5で作業している間に-Microsoftは.net 5以上を作成しているので、このソート方法は今後壊れないだろうと推測できます。

もちろん、.net9にアップグレードする2017年までに互換性の問題が発生する可能性がありますが、その頃にはMicrosoftのスタッフはおそらく「公式のソートメカニズム」を理解するでしょう。戻ったり物を壊したりするのは理にかなっていない。

プロパティの順序付けのために追加の属性を使用する場合も、時間と実装が必要になります。MetadataTokenの並べ替えが機能するのに気にする必要はありません

4
TarmoPikaro

カスタム属性の代わりに、System.Component.DataAnnotationsでDisplayAttributeを使用できます。とにかく、ディスプレイで何かをする必要があります。

1
Panos Roditakis

私はこのようにしました:

 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
Sentinel

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

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

metaData.GetFields();
0
Tom Makin

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

指定

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

Note、エラーチェックやフォールトトレランスはありません。塩味

0
TheGeneral