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

클래스에서 선언 된 순서대로 리플렉션을 사용하여 모든 속성을 가져와야합니다. 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].


답변

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

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


답변

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

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

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

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

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


답변

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




    }

다음과 같이 선언 된 속성으로 :

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

}