Nhận thuộc tính theo thứ tự khai báo bằng cách sử dụng phản chiếu


81

Tôi cần lấy tất cả các thuộc tính bằng cách sử dụng phản chiếu theo thứ tự mà chúng được khai báo trong lớp. Theo MSDN, thứ tự không thể được đảm bảo khi sử dụngGetProperties()

Phương thức GetProperties không trả về thuộc tính theo một thứ tự cụ thể, chẳng hạn như thứ tự bảng chữ cái hoặc thứ tự khai báo.

Nhưng tôi đã đọc rằng có một cách giải quyết bằng cách sắp xếp các thuộc tính theo MetadataToken. Vì vậy, câu hỏi của tôi là, đó là an toàn? Tôi dường như không thể tìm thấy bất kỳ thông tin nào trên MSDN về nó. Hoặc có cách nào khác để giải quyết vấn đề này?

Triển khai hiện tại của tôi trông như sau:

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

12
Dù sao, đó là một ý tưởng tồi. Tạo thuộc tính của riêng bạn với giá trị đơn hàng hoặc bất kỳ siêu dữ liệu nào khác và đánh dấu các trường của lớp bằng thuộc tính đó.
Kirill Polishchuk,

1
Có lẽ bạn có thể thêm một thuộc tính mới chứa một số nguyên của thứ tự. Sau đó lấy các thuộc tính, lấy DisplayOrderAttribute của mỗi thuộc tính và sắp xếp theo điều đó?
BlueChippy

1
Vì tò mò, tại sao bạn lại làm điều này, bạn đang cố gắng đạt được điều gì?
Sam Greenhalgh

6
@Magnus Tuy nhiên, câu hỏi vẫn là một câu hỏi thú vị vì bản thân một số phần của framework phụ thuộc rất nhiều vào điều này. Ví dụ tuần tự hóa với thuộc tính Serializable lưu trữ các thành viên theo thứ tự mà chúng đã được xác định. Tại leas Wagner khẳng định này trong cuốn sách của ông "Effective C #"
Pavel Voronin

Câu trả lời:


143

Trên .net 4.5 (và thậm chí .net 4.0 so với 2012), bạn có thể làm tốt hơn nhiều với việc phản chiếu bằng cách sử dụng thủ thuật thông minh với [CallerLineNumber]thuộc tính, cho phép trình biên dịch chèn thứ tự vào thuộc tính của bạn cho bạn:

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

Và sau đó sử dụng phản chiếu:

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)
{
   //
}

Nếu bạn phải xử lý các lớp từng phần, bạn có thể sắp xếp bổ sung các thuộc tính bằng cách sử dụng [CallerFilePath].


2
Điều này thực sự rất thông minh! Tôi tự hỏi nếu nó có bất kỳ lập luận phản đối chống lại nó? Thực ra có vẻ khá thanh lịch đối với tôi. Tôi đang sử dụng Linq2CSV và tôi nghĩ rằng tôi sẽ kế thừa từ CsvColumnAttributevà sử dụng như mặc định FieldIndexgiá trị
julealgon

2
@julealgon Tôi cho rằng lập luận chống lại điều này là có một API vị trí , không có giấy tờ mà ai đó có thể phá vỡ khi cấu trúc lại nếu họ không hiểu rằng thuộc tính đang được sử dụng theo cách này. Tôi vẫn nghĩ rằng nó khá thanh lịch, chỉ nói trong trường hợp ai đó sao chép / dán cái này và đang tìm kiếm động lực để lại một số bình luận cho anh chàng tiếp theo.
Joel B

2
Điều này hoạt động trừ trường hợp 1 lớp kế thừa một lớp khác và tôi cần thứ tự của tất cả các thuộc tính. Có một thủ thuật cho điều đó là tốt?
Paul Baxter

Tôi nghĩ rằng điều này sẽ bị phá vỡ nếu lớp được khai báo một phần. Không giống như kế thừa, API không thể tìm ra nó.
Wu Zhenwei

1
Cách tiếp cận rất thông minh nhưng nó sẽ ném ra ngoại lệ tham chiếu rỗng nếu một số người quên đặt thuộc tính [Order]. Tốt hơn nên kiểm tra null trước khi gọi. Ví dụ: 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 ()) làm thuộc tính OrderAttribute order theo orderAttribute.Order chọn thuộc tính;
Sopan Maiti

15

Nếu bạn đang đi theo tuyến thuộc tính, đây là một phương pháp tôi đã sử dụng trước đây;

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

Sau đó sử dụng nó như thế này;

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

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

Ở đâu;

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

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

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

Phương thức sẽ bị lỗi nếu bạn chạy nó trên một kiểu không có các thuộc tính có thể so sánh rõ ràng trên tất cả các thuộc tính của bạn, vì vậy hãy cẩn thận cách sử dụng nó và nó phải đủ cho yêu cầu.

Tôi đã bỏ qua định nghĩa về Order: Attribute vì có một mẫu hay trong liên kết của Yahia tới bài đăng của Marc Gravell.


2
Nếu đây là yêu cầu hiển thị, sẽ thích hợp sử dụng DataAnnotations.DisplayAttribute , có trường Đơn hàng.
Jerph

12

Theo MSDN MetadataToken là duy nhất bên trong một Mô-đun - không có gì nói rằng nó đảm bảo bất kỳ thứ tự nào cả.

NGAY CẢ nếu nó hoạt động theo cách bạn muốn nó sẽ được triển khai cụ thể và có thể thay đổi bất cứ lúc nào mà không cần thông báo.

Xem mục blog MSDN cũ này .

Tôi thực sự khuyên bạn nên tránh xa bất kỳ sự phụ thuộc nào vào các chi tiết triển khai như vậy - hãy xem câu trả lời này từ Marc Gravell .

NẾU bạn cần một cái gì đó tại thời điểm biên dịch, bạn có thể xem qua Roslyn (mặc dù nó đang ở giai đoạn rất sớm).


5

Những gì tôi đã thử nghiệm sắp xếp theo MetadataToken hoạt động.

Một số người dùng ở đây khẳng định đây là cách tiếp cận không tốt / không đáng tin cậy bằng cách nào đó, nhưng tôi chưa thấy bất kỳ bằng chứng nào về điều đó - có lẽ bạn có thể đăng một số đoạn mã ở đây khi cách tiếp cận đưa ra không hoạt động?

Về khả năng tương thích ngược - trong khi bạn hiện đang làm việc trên .net 4 / .net 4.5 - Microsoft đang sản xuất .net 5 trở lên, vì vậy bạn có thể cho rằng phương pháp sắp xếp này sẽ không bị phá vỡ trong tương lai.

Tất nhiên có thể vào năm 2017, khi bạn nâng cấp lên .net9, bạn sẽ bị phá vỡ khả năng tương thích, nhưng vào thời điểm đó các nhà Microsoft có thể sẽ tìm ra "cơ chế sắp xếp chính thức". Không có ý nghĩa gì khi quay lại hoặc phá vỡ mọi thứ.

Chơi với các thuộc tính bổ sung để sắp xếp thuộc tính cũng cần thời gian và việc triển khai - tại sao phải bận tâm nếu phân loại MetadataToken hoạt động?


1
Bây giờ là năm 2019 và thậm chí .net-4.9 vẫn chưa được phát hành :-p.
binki

Mỗi lần .net phát hành một phiên bản chính là một cơ hội tuyệt vời để họ thực hiện thay đổi đối với những thứ như MetadataToken hoặc thứ tự trả lại từ đó GetProperties(). Lập luận của bạn về lý do tại sao bạn có thể dựa vào thứ tự giống hệt như lập luận về lý do tại sao bạn không thể dựa vào các phiên bản tương lai của .net không thay đổi hành vi này: mọi bản phát hành mới đều có thể tự do thay đổi chi tiết triển khai. Bây giờ, .net thực sự tuân theo triết lý “lỗi là tính năng”, vì vậy trong thực tế, chúng có thể không bao giờ thay đổi thứ tự của GetProperties(). Chỉ là API nói rằng họ được phép.
binki

1

Bạn có thể sử dụng DisplayAttribute trong System.Component.DataAnnotations, thay vì thuộc tính tùy chỉnh. Yêu cầu của bạn là phải làm gì đó với màn hình.


0

Nếu bạn hài lòng với sự phụ thuộc bổ sung, có thể sử dụng Protobuf-Net của Marc Gravell để làm điều này mà không cần phải lo lắng về cách tốt nhất để thực hiện phản chiếu và bộ nhớ đệm, v.v. Chỉ cần trang trí các trường của bạn bằng cách sử dụng [ProtoMember]và sau đó truy cập các trường theo thứ tự số bằng cách sử dụng:

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

metaData.GetFields();

0

Tôi đã làm theo cách này:

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




    }

với tài sản được kê khai như sau:

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

}

Câu trả lời này khác hay tốt hơn câu trả lời được chấp nhận như thế nào?
Ian Kemp

0

Xây dựng trên giải pháp được chấp nhận ở trên, để có được Chỉ mục chính xác, bạn có thể sử dụng một cái gì đó như thế này

Được

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

Phần mở rộng

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

Sử dụng

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

Lưu ý , không có lỗi kiểm tra hoặc chịu lỗi, bạn có thể thêm tiêu và muối cho vừa miệng


0

Một khả năng khác là sử dụng System.ComponentModel.DataAnnotations.DisplayAttribute Ordertài sản. Vì nó là nội trang, không cần tạo một thuộc tính cụ thể mới.

Sau đó chọn các thuộc tính có thứ tự như thế này

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

Và lớp học có thể được trình bày như thế này

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

    [Display(Name = "Description", Order = 1)
    public string Label {get; set; }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.