developer tip

리플렉션을 사용하여 선언 순서대로 속성 가져 오기

copycodes 2020. 10. 26. 08:05
반응형

리플렉션을 사용하여 선언 순서대로 속성 가져 오기


클래스에서 선언 된 순서대로 리플렉션을 사용하여 모든 속성을 가져와야합니다. MSDN에 따르면 사용시 주문이 보장되지 않습니다.GetProperties()

GetProperties 메서드는 사전 순 또는 선언 순서와 같은 특정 순서로 속성을 반환하지 않습니다.

하지만 .NET Core로 속성을 주문하는 방법이 있다는 것을 읽었습니다 MetadataToken. 제 질문은 안전한가요? MSDN에 대한 정보를 찾을 수없는 것 같습니다. 아니면이 문제를 해결하는 다른 방법이 있습니까?

내 현재 구현은 다음과 같습니다.

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

.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].


MSDN 에 따르면 MetadataToken하나의 모듈 내에서 고유합니다. 모든 주문을 보장한다는 말은 없습니다.

원하는 방식으로 작동하더라도 구현에 따라 다르며 예고없이 언제든지 변경 될 수 있습니다.

이 오래된 MSDN 블로그 항목을 참조하십시오 .

이러한 구현 세부 사항에 대한 종속성을 피하는 것이 좋습니다 . Marc Gravell의이 답변을 참조하십시오 .

컴파일 타임에 필요한 것이 있다면 Roslyn을 살펴볼 수 있습니다 (아주 초기 단계 임에도 불구하고).


속성 경로를 사용하는 경우 여기에 내가 과거에 사용한 방법이 있습니다.

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의 Marc Gravell의 포스트에 대한 좋은 샘플이 있기 때문이다.


내가 MetadataToken으로 정렬을 테스트 한 것은 작동합니다.

여기에있는 사용자 중 일부는 이것이 어떻게 든 좋은 접근 방식이 아니거나 신뢰할 수 없다고 주장하지만 아직 그 증거를 보지 못했습니다. 주어진 접근 방식이 작동하지 않을 때 여기에 코드 스 니펫을 게시 할 수 있습니까?

이전 버전과의 호환성에 대해-현재 .net 4 / .net 4.5에서 작업하는 동안-Microsoft는 .net 5 이상을 만들고 있으므로이 정렬 방법이 향후에 손상되지 않을 것이라고 예상 할 수 있습니다.

물론 2017 년이되면 .net9로 업그레이드 할 때 호환성이 중단 될 것입니다.하지만 그때 쯤이면 Microsoft 직원이 "공식 정렬 메커니즘"을 알아낼 것입니다. 돌아가거나 물건을 부수는 것은 의미가 없습니다.

속성 순서 지정을위한 추가 속성을 사용하는 것도 시간과 구현이 필요합니다. MetadataToken 정렬이 작동하면 왜 귀찮게해야합니까?


사용자 지정 특성 대신 System.Component.DataAnnotations에서 DisplayAttribute를 사용할 수 있습니다. 어쨌든 당신의 요구 사항은 디스플레이에 뭔가를해야합니다.


추가 종속성에 만족한다면 Marc Gravell의 Protobuf-Net 을 사용하여 리플렉션 및 캐싱 등을 구현하는 가장 좋은 방법에 대해 걱정할 필요없이이 작업을 수행 할 수 있습니다. [ProtoMember]다음을 사용하여 필드를 장식 한 다음 다음을 사용하여 필드에 액세스합니다.

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

metaData.GetFields();

나는 이렇게했다 :

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




    }

with the property declared as follows:

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

}

Building on the above accepted solution, to get the exact Index you could use something like this

Given

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

Extensions

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

Usage

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

Note, there is no error checking or fault tolerance, you can add pepper and salt to taste

참고URL : https://stackoverflow.com/questions/9062235/get-properties-in-order-of-declaration-using-reflection

반응형