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


81

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

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

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

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

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

12
어쨌든 나쁜 생각입니다. 주문 값 또는 기타 메타 데이터로 고유 한 속성을 만들고 해당 속성으로 클래스의 필드를 표시합니다.
Kirill Polishchuk 2012 년

1
주문의 정수를 포함하는 새 속성을 추가 할 수 있습니다. 그런 다음 속성을 가져오고 각 속성의 DisplayOrderAttribute를 가져 와서 정렬 하시겠습니까?
BlueChippy 2012 년

1
호기심에 왜 이러는거야?
Sam Greenhalgh

6
@Magnus 그러나 프레임 워크 자체의 일부가 이것에 크게 의존하기 때문에 질문은 여전히 ​​흥미로운 것입니다. 예를 들어 Serializable 속성을 사용한 직렬화는 정의 된 순서대로 멤버를 저장합니다. 좋은 초원에서 바그너이 그의 책에서 "효과적인 C #을"상태
파벨 보로닌에게

답변:


143

.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

2
@julealgon 이에 대한 주장은 누군가가 리팩토링 할 때 속성이 이런 방식으로 사용되는 것을 이해하지 못하면 깨질 수 있는 문서화되지 않은 위치 API가 있다는 것입니다. 누군가가 이것을 복사 / 붙여 넣기하고 다음 사람에게 댓글을 남길 동기를 찾고있는 경우를 대비하여 여전히 꽤 우아하다고 생각합니다.
Joel B

2
이것은 한 클래스가 다른 클래스를 상속하고 모든 속성의 순서가 필요한 경우를 제외하고는 작동합니다. 그것에 대한 트릭도 있습니까?
Paul Baxter

클래스가 부분적으로 선언되면 이것이 깨질 것이라고 생각합니다. 상속과 달리 API는이를 파악할 수 없습니다.
Wu Zhenwei 2017-06-24

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.Order 선택 속성;
Sopan Maiti

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


2
이것이 표시 요구 사항 인 경우 Order 필드가있는 DataAnnotations.DisplayAttribute 를 사용하는 것이 적절 합니다.
Jerph 2013

12

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

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

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

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

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


5

MetadataToken으로 정렬하여 테스트 한 내용이 작동합니다.

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

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

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

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


1
2019 년이고 .net-4.9도 아직 출시되지 않았습니다 :-p.
binki

.net이 메이저 버전을 릴리스 할 때마다 MetadataToken 또는 .NET의 반환 순서와 같은 항목을 변경할 수있는 좋은 기회입니다 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_; } }

}

이것이 허용되는 답변과 어떻게 다르거 나 더 낫습니까?
Ian Kemp

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.