我有一个接口,它定义了一个返回IList<PropertyInfo>
的方法:
public interface IWriteable
{
IList<PropertyInfo> WriteableProperties();
}
。
。它以以下方式在各种(不同的)类中实现:
public abstract class Foo
{
private IList<PropertyInfo> _props;
protected Foo()
{
this._props = new List<PropertyInfo>();
foreach (PropertyInfo p in this.GetType().GetProperties())
{
if (Attribute.IsDefined(p, typeof(WriteableAttribute)))
this._props.Add(p);
}
}
#region IWriteable Members
public IList<PropertyInfo> WriteableProperties()
{
return this._props;
}
#endregion
}
public class Bar : Foo
{
public string A
{
get { return "A"; }
}
[Writeable()]
public string B
{
get { return "B"; }
}
[Writeable()]
public string C
{
get { return "C"; }
}
// Snip
}
请注意标记几个属性的属性,因为这些属性将被添加到列表中。然后,这个IList
将在一些文件写操作期间在其他地方使用。
对我来说,重要的是它们在列表中按照它们在代码文件中出现的顺序排列。
但是,MSDN显示:
GetProperties方法不返回特定的属性顺序,如字母顺序或声明顺序。你的代码不能取决于属性返回的顺序,因为订单变化。
那么,确保每个PropertyInfo按我想要的顺序添加的最佳方法是什么?
(我也在使用。net 2.0,所以我不能使用任何Linq的优点,如果有任何帮助的话,尽管它会很有趣。)
向属性中添加有关排序的信息,然后您可以使用这些信息来确保排序,例如:
[Writeable(Order = 1)]
那么对于下面的属性:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class WriteableAttribute : Attribute
{
public int Order { get; set; }
}
你可以得到一个有序的属性选择,如下所示:
private readonly List<PropertyInfo> _props;
protected Foo()
{
_props = new List<PropertyInfo>();
var props = new Dictionary<int, PropertyInfo>();
foreach (PropertyInfo p in GetType().GetProperties())
{
if (Attribute.IsDefined(p, typeof(WriteableAttribute)))
{
var attr = (WriteableAttribute)p
.GetCustomAttributes(typeof(WriteableAttribute))[0];
props.Add(attr.Order, p);
}
}
_props.AddRange(props.OrderBy(kvp => kvp.Key).Select(kvp => kvp.Value));
}
NB对于生产代码,我建议缓存属性信息(例如每个类型),因为如果为每个实例执行,这将相对较慢。
Update - Caching
有一些缓存属性查找和排序的例子:
public static class PropertyReflector
{
private static readonly object SyncObj = new object();
private static readonly Dictionary<Type, List<PropertyInfo>> PropLookup =
new Dictionary<Type, List<PropertyInfo>>();
public static IList<PropertyInfo> GetWritableProperties(Type type)
{
lock (SyncObj)
{
List<PropertyInfo> props;
if (!PropLookup.TryGetValue(type, out props))
{
var propsOrder = new Dictionary<int, PropertyInfo>();
foreach (PropertyInfo p in type.GetProperties())
{
if (Attribute.IsDefined(p, typeof(WriteableAttribute)))
{
var attr = (WriteableAttribute)p.GetCustomAttributes(
typeof(WriteableAttribute), inherit: true)[0];
propsOrder.Add(attr.Order, p);
}
}
props = new List<PropertyInfo>(propsOrder
.OrderBy(kvp => kvp.Key)
.Select(kvp => kvp.Value));
PropLookup.Add(type, props);
}
return props;
}
}
}
Update - No Linq
您可以用以下代码替换Linq部分来排序属性并将它们添加到缓存中:
List<int> order = new List<int>(propsOrder.Keys);
order.Sort();
props = new List<PropertyInfo>();
order.ForEach(i => props.Add(propsOrder[i]));
PropLookup.Add(type, props);
Update - Full Linq
和使用完整的Linq解决方案:
static IList<PropertyInfo> GetWritableProperties(Type type)
{
lock (SyncObj)
{
List<PropertyInfo> props;
if (!PropLookup.TryGetValue(type, out props))
{
props = type.GetProperties()
.Select(p => new { p, Atts = p.GetCustomAttributes(typeof(WriteableAttribute), inherit: true) })
.Where(p => p.Atts.Length != 0)
.OrderBy(p => ((WriteableAttribute)p.Atts[0]).Order)
.Select(p => p.p)
.ToList();
PropLookup.Add(type, props);
}
return props;
}
}
前一段时间,当我有同样的问题,我写了一个助手类来排序基于属性的Order
属性的属性。我使用了内置的DisplayAttribute
,但你可以添加一个Order
属性到你写的任何属性。
class FieldSorter : IComparer, IComparer<DisplayAttribute>, IEqualityComparer<DisplayAttribute>
{
public int Compare(object x, object y)
{
return Compare((DisplayAttribute)x, (DisplayAttribute)y);
}
public int Compare(DisplayAttribute x, DisplayAttribute y)
{
return x.Order.CompareTo(y.Order);
}
public bool Equals(DisplayAttribute x, DisplayAttribute y)
{
return Compare(x, y) == 0;
}
public int GetHashCode(DisplayAttribute obj)
{
return obj.GetHashCode();
}
public static SortedList<DisplayAttribute, PropertyInfo> GetSortedFields(Type type)
{
PropertyInfo[] props = type.GetProperties();
var sortedProps = new SortedList<DisplayAttribute, PropertyInfo>(props.Length, new FieldSorter());
object[] atts;
int assignedOrder = 1000; // anything without pre-assigned order gets a ridiculously high order value. same for duplicates.
foreach (var prop in props)
{
atts = prop.GetCustomAttributes(typeof(DisplayAttribute), true);
if (atts.Length > 0)
{
var att = (DisplayAttribute)atts[0];
if (!att.GetOrder().HasValue || sortedProps.Keys.Contains(att, new FieldSorter()))
att.Order = assignedOrder++;
sortedProps.Add(att, prop);
}
}
return sortedProps;
}
}
这给了您一个SortedList
,其中键是属性,值是PropertyInfo。这是因为我仍然需要访问该属性的其他属性。
使用例子:
public class Stats
{
[Display(Name = "Changes", Description = "Changed records.", Order = 8)]
public int RecordsWithChanges { get; set; }
[Display(Name = "Invalid", Description = "Number of invalid records analyzed.", Order = 4)]
public int InvalidRecordCount { get; set; }
[Display(Name = "Valid", Description = "Number of valid records.", Order = 6)]
public int ValidRecordCount { get; set; }
[Display(Name = "Cost", Description = "Number of records with a Cost value.", Order = 10)]
public int RecordsWithCost { get; set; }
public Stats(int changed, int valid, int invalid, int cost)
{
RecordsWithChanges = changed;
ValidRecordCount = valid;
InvalidRecordCount = invalid;
RecordsWithCost = cost;
}
}
class Program
{
static void Main(string[] args)
{
var foo = new Stats(123, 456, 7, 89);
var fields = FieldSorter.GetSortedFields(foo.GetType());
foreach (DisplayAttribute att in fields.Keys)
Console.WriteLine("{0}: {1} ({2}) == {3}",
att.Order, att.Name, att.Description, fields[att].GetValue(foo, null));
null));
}
}
输出:4: Invalid (Number of invalid records analyzed.) -- 7 6: Valid (Number of valid records.) -- 456 8: Changes (Changed records.) -- 123 10: Cost (Number of records with a Cost value.) -- 89