如何按自定义属性对类型属性进行排序



我创建了一个自定义属性,该属性实现了几个属性,这些属性将接受其使用值,如下所示。

public class CustomAttribute : OrderAttribute
{
public string Name { get; set; }
public bool Ignore { get; set; }
public override int Order { get; set; } = -1;
}
public abstract OrderAttribute : Attribute
{
public virtual int Order { get; set; }
}

之后,我创建了类模型来使用CustomAttribute

public abstract class Person
{
[Custom(Name = "Frist Name")]
public string FirstName { get; set; }
[Custom(Name = "Last Name")]
public string LastName { get; set; }
[Custom(Name = "Email")]
public string Email { get; set; }
}
public class Student : Person
{
[Custom(Name = "Address", Order = 2)]
public string Address { get; set; }
[Custom(Name = "Grade", Order = 5)]
public int Grade { get; set; }
}

由于我将其与 C# 反射一起使用,因此我必须使用typeof并获取类属性。这是我必须使用OrderBy根据属性定义的顺序对属性的顺序进行排序的时候。

typeof(Student).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.OrderBy(KeySelector)
.ToList();

OrderBy中使用此方法。

private static int KeySelector(PropertyInfo prop)
{
var attr = prop.GetCustomAttribute<CustomAttribute>();
return attr?.Order ?? -1;
}

但是,它的作用是这样的:

  1. 姓氏
  2. 电子邮件
  3. 地址
  4. 年级

我想要的是,对于任何具有[Custom(Name = "Test")]且未实现 Order 属性的属性,将保留在其订单上或默认订单值为-1的属性。所以顺序应该是这样的:

  1. 地址
  2. 姓氏
  3. 电子邮件
  4. 年级

这是我的解决方案,它有点冗长,因为正如评论中提到的,GetProperties结果的顺序无法保证。对于 .NET 4.5+,有一种方法可以解决这个问题,如本答案所述。

因此,第一部分是确保我们可以以可预测的顺序获取类及其基类的属性,而不管我们的自定义顺序如何。为此,我们从基类向上递归并收集每个基类中声明的属性,按属性在类中出现的顺序对结果进行排序。

public static IEnumerable<PropertyMetaData> GetPropertiesOrdered(Type someType, int inheritanceLevel = 1)
{
List<PropertyMetaData> seenProperties = new List<PropertyMetaData>();
if (someType.BaseType != (typeof(object)))
seenProperties.AddRange(GetPropertiesOrdered(someType.BaseType, inheritanceLevel + 1));
var properties = someType
.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
.Select(a => new PropertyMetaData(a, inheritanceLevel))
.Where(a => a.AttributeData != null);
properties = properties
.OrderBy(a => a.AttributeData.ClassOrder)
.Select((a, ordinal) =>
{
a.OrderWithinClass = ordinal + 1;
return a;
});                        
return seenProperties
.Union(properties)
.OrderByDescending(a => a.InheritanceLevel)
.ThenBy(a => a.OrderWithinClass)
.Select((a, ordinal) =>
{
a.OrderOverall = ordinal + 1;
return a;
});
}

为了支持这一点,属性略有更改,如下所示,以使用编译器行号属性,如应答链接中所示:

public class CustomAttribute : OrderAttribute
{
public CustomAttribute([CallerLineNumber]int order = 0) : base(order)
{
}
public string Name { get; set; }
public bool Ignore { get; set; }
public override int Order { get; set; } = -1;
}
public abstract class OrderAttribute : Attribute
{
private readonly int _classOrder;
public OrderAttribute([CallerLineNumber]int order = 0)
{
_classOrder = order;
}
public int ClassOrder { get { return _classOrder;  } }
public virtual int Order { get; set; }
}

因此,现在我们以可预测的编号顺序获得了现有属性,从基类Person属性开始,然后是顶级Student类属性。GetPropertiesOrdered方法的输出是一个容器类,其中包含有关所遇到属性的顺序、与每个属性关联的自定义属性以及用于处理自定义排序的代码的信息 - 也就是说,如果定义了顺序,则首选它而不是类型中看到的属性的顺序。

public class PropertyMetaData : IComparable<PropertyMetaData>
{
public PropertyMetaData(PropertyInfo propertyInfo, int inheritanceLevel)
{
InheritanceLevel = inheritanceLevel;
PropertyInfo = propertyInfo;
AttributeData = propertyInfo.GetCustomAttribute<CustomAttribute>();
}
public int InheritanceLevel { get; set; }
public int OrderWithinClass { get; set; }
public int OrderOverall { get; set; }
public CustomAttribute AttributeData { get; set; }            
public PropertyInfo PropertyInfo { get; set; }
public int GetOrder()
{
return HasCustomOrder() ? AttributeData.Order : this.OrderOverall;
}
public bool HasCustomOrder()
{
return AttributeData.Order != -1;
}
public int CompareTo(PropertyMetaData other)
{
var myOrder = GetOrder();
var otherOrder = other.GetOrder();
int compare = myOrder.CompareTo(otherOrder);
if (compare != 0 || other == this) return compare;
if (HasCustomOrder() && other.HasCustomOrder()) return 0;
if (HasCustomOrder() && !other.HasCustomOrder()) return -1;
return 1;
}
}

把它放在一起可以这样称呼:

var propertiesSorted =
GetPropertiesOrdered(typeof(Student))
.OrderBy(a => a);

这将按照您示例所需的顺序返回字段: https://dotnetfiddle.net/dd5hVN

最新更新