目录
二、自定义Attribute:DisplayIndexAttribute
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
ItemsControl控件(含ComboBox,ContextMenu,ListBox,ListView,Menu,StatusBar,TabControl,
TreeView)设置ItemSource后,想查看多个绑定对象多个成员的变量,不设置Path属性就能实现这功能。但显示的字串太长,很多内容重复,我们想看具体某几个成员的值,必须重写绑定对象的ToString()。当对象一多,或者控件多,每次调试都要重写几个ToString,算起来也花不少时间。那么,写一个能按标志顺序显示成员值的基类,不就所有派生类都有这功能么?
思路如下:1 定义一个静态表DisplayDataTable,存放需要按序显示的成员(property ,field),表中3列,第一列表示该行是property还是field,第二列存放成员名字,第三列存放带有DisplayIndexAttribute的值,用于显示顺序。利用DataTableView.Sort给成员排序,如果索引值相同,则按成员名字排序。
2 因为DisplayDataTable是静态成员,在基类、派生类中是共享一块地址,也就是说,派生类对DisplayDataTable的更改会对基类访问该表记录检索成员时,会检索到派生类的成员,这肯定会引发一系列异常。那么如何使基类、派生类有同名静态成员,而不互相影响呢。.Net 为我们提供了一个关键字 new 就派上用场了。派生类中 一定要隐藏基类静态成员,public static new DataTable DisplayDataTable = new DataTable();。
3 非静态函数访问DisplayDataTable时,早期我直接写DisplayDataTable,想利用多态性让DisplayDataTable自动被翻译为 派生类.DisplayDataTable,但调试过程让我异常痛苦。最后得出的结论是,程序从一开始,如果先定义基类,那么DisplayDataTable永远就是基类的表,如果先定义派生类,那么DisplayDataTable就永远是该派生类的数据。就是说DisplayDataTable在第一个派生类定义下来后,所有类中不加类名限定访问静态成员,该值就是第一个定义类的值。及使使用 new 关键字隐藏基类静态成员。总算了解微软说静态成员不支持继承/多态的形式。
4 第3点的问题确实很难解决。既然不加前缀使用静态成员DisplayDataTable会导致混乱,那么给静态成员加上this前缀不就行了。可惜静态成员的前缀只能是类名。难道派生类要复制基类的代码来改静态成员的前缀,那这样派生继承还有啥意义。 幸好.Net提供了反射,我们可以利用反射提供类名来约束,再通过检索获取静态成员的值。这样间接的给静态成员加上了this前缀,我们访问这些静态成员时,可以摆脱 [类名.静态成员]这种格式。最后,为了防止其他地方不规范直接访问静态成员,我们给DisplayDataTable写了一个GetDisplayData方法,作为唯一合法的访问静态成员的入口。以后访问该静态成员[(本类名).DisplayDataTable]就必须写成[this.GetDisplayData()]
一、先给Object扩展
获取对象的Property、Field名字和值,需要引用System.Reflection名字空间。
using System.Reflection;
public static class ObjectExtend
{
/// <summary>
/// 取得propertyName指定Property的值
/// </summary>
/// <param name="obj"></param>
/// <param name="propertyName"></param>
/// <returns></returns>
public static Object GetPropertyValue(this Object obj, string propertyName)
{
PropertyInfo pi = obj.GetType().GetProperty(propertyName);
object r = pi?.GetValue(obj); //防止获取空值出错
return r;
}
/// <summary>
/// 取得fieldName指定field的值
/// </summary>
/// <param name="obj"></param>
/// <param name="fieldName"></param>
/// <returns></returns>
public static object GetFieldValue(this object obj, string fieldName)
{
FieldInfo pi = obj.GetType().GetField(fieldName);
object r = pi?.GetValue(obj); //防止获取空值出错 等价 r=pi!=null?pi.GetValue(obj):null;
return r;
}
}
二、自定义Attribute:DisplayIndexAttribute
自定义Attribute,用来标志对象Property或Field的显示顺序。
#region 给对象Property或Field标识,确定ToString()显示顺序 的 Atturbite
/// <summary>
/// 给对象Property或Field标识,ToString()显示顺序
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Constructor, AllowMultiple = false, Inherited = true)]
//AttributeTargets:属性应用到的目标类型。AllowMultiple:是否允许一个元素应用多个此属性。Inherited:属性能否有派生类继承。
public class DisplayIndexAttribute : Attribute
{
public int Index { get; set; }
public DisplayIndexAttribute(int index)
{ this.Index = index; }
}
#endregion
三、 重写ToString()的基类
下面是实现功能的基类。写该类时,为了维护每个类的显示属性排序,使用了DataTable对象,不想让每个对象都带着一个DataTable,把该DataTable定义为Static DataTable displayIndexAttributeTable。最后测试时,发现最先引用到该表的类实例化该表后,后面其他根类、派生类,都使用该表而没重新实例化。就是说父类,子类,孙类树上的任何一个兄弟类,同名的Static 变量都指向同一块内存。一个类更改 Static变量,其它相关类都能接收到。
/// <summary>
/// 重写ToString(),使其显示按DisplayIndexAttribute标志排序的Property或Field的值
/// 派生类要用new 隐藏静态成员 DisplayDataTable
/// </summary>
public class ToStringWithDisplayIndex
{
[DisplayIndexAttribute(0)]
public string T0_I_0 = "";
protected static DataTable DisplayDataTable=new DataTable();
public virtual string[] FieldsName()
{
BindingFlags flag = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;
FieldInfo[] fif = GetType().GetFields(flag);
string[] r = new string[fif.Length];
for (int i = 0; i < fif.Length; i++)
{
r[i] = fif[i].Name;
}
return r;
}
public virtual string[] ProsName()
{
PropertyInfo[] fif = GetType().GetProperties();
string[] r = new string[fif.Length];
for (int i = 0; i < fif.Length; i++)
{
r[i] = fif[i].Name;
}
return r;
}
/// <summary>
/// 通过反射取得本类的DisplayDataTable,方便实例成员使用this访问,而不需要ClassName.DisplayDataTable导致只能访问基类静态成员
/// </summary>
/// <returns></returns>
protected DataTable GetDisplayData()
{
BindingFlags flag = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Public;
FieldInfo fi = this.GetType().GetField("DisplayDataTable", flag);
DataTable tb = (DataTable)fi.GetValue(this);
return tb;
}
/// <summary>
/// 确保第一次引用DisplayDataTable 实例化,也是DisplayDataTable唯一合法访问器
/// </summary>
/// <returns></returns>
protected DataView GetDisplayView()
{
FillTalbe();
DataView dv = new DataView(this.GetDisplayData());
dv.Sort= string.Format("{0},{1}", col_attributeIndex, col_name);
return dv;
}
public override string ToString()
{
StringBuilder r = new StringBuilder();
for (int i = 0; i < this.GetDisplayView().Count; i++)
{
DataRowView drv = this.GetDisplayView()[i];
string pName = drv[col_name].ToString();
object vl = drv[col_porpertyOrField].ToString() == "porperty" ? this.GetPropertyValue(pName) : this.GetFieldValue(pName);
r.Append(string.Format("{0}:{1}; ", pName, vl));
}
return r.ToString();
}
#region 列
/// <summary>
/// 指示 (存放 property 或者 field )列的列名
/// </summary>
protected static readonly string col_porpertyOrField = "porpertyOrField";
/// <summary>
/// 指示 (存放 propertyName 或者 fieldName) 列的列名
/// </summary>
protected static readonly string col_name = "name";
/// <summary>
/// 指示 (存放 value) 列的列名
/// </summary>
protected static readonly string col_attributeIndex = "attributeIndex";
#endregion
/// <summary>
/// 填充DisplayDataTable
/// </summary>
protected virtual void FillTalbe()
{
DataTable tb = this. GetDisplayData();
//确保只填充一次,也只有一次。
if (tb .Columns.Count != 0) return;
tb. Columns.AddRange(new DataColumn[] { new DataColumn(col_porpertyOrField, typeof(string)),
new DataColumn(col_name, typeof(string)),
new DataColumn(col_attributeIndex, typeof(int)) });
GC.Collect(0);//回收丢弃的Table
PropertyInfo[] propertys = this.GetType().GetProperties();
foreach (PropertyInfo p in propertys)
{
DisplayIndexAttribute PindexAttribute = (DisplayIndexAttribute)p.GetCustomAttribute(typeof(DisplayIndexAttribute), false);
if (PindexAttribute != null)
{
DataRow row = tb.NewRow();
row[col_porpertyOrField] = "porperty";
row[col_name] = p.Name;
row[col_attributeIndex] = PindexAttribute.Index;
tb.Rows.Add(row);
}
}
FieldInfo[] fields = this.GetType().GetFields();
foreach (FieldInfo f in fields)
{
DisplayIndexAttribute PindexAttribute = (DisplayIndexAttribute)f.GetCustomAttribute(typeof(DisplayIndexAttribute), false);
if (PindexAttribute != null)
{
DataRow row = tb.NewRow();
row[col_porpertyOrField] = "field";
row[col_name] = f.Name;
row[col_attributeIndex] = PindexAttribute.Index;
tb.Rows.Add(row);
}
}
}
}
四、测试代码
public class T1: ToStringWithDisplayIndex
{
[DisplayIndexAttribute(1)]
public string T1_I_1 ="T1";
//故意不隐藏 DisplayDataTable,可以引发错误。
}
public class T2: ToStringWithDisplayIndex
{
[DisplayIndexAttribute(0)]
public string T2_I_0 = "T2";
public static new DataTable DisplayDataTable = new DataTable();
}
public class T2_1 : T2
{
[DisplayIndexAttribute(0)]
public string T = "T1_1";
public static new DataTable DisplayDataTable = new DataTable();
}
static void Main(string[] args)
{
ToStringWithDisplayIndex t0 = new ToStringWithDisplayIndex() ;
T1 t1 = new T1() ;
T2 t2 = new T2();
T2_1 t2_1 = new T2_1();
Console.WriteLine("Columns");
Console.WriteLine(t0.ToString());
Console.WriteLine(t1.ToString());
Console.WriteLine(t2.ToString());
Console.WriteLine(t2_1.ToString());
Console.Read();
}
结果:
可以看到 T1 类虽然有定义新的成员,但没有隐藏静态成员DisplayDataTable,得到的信息和基类一样,只有一个T0_I_0的附加DisplayIndexAttribute成员. T2 T2_1有使用new 隐藏静态成员DisplayDataTable,得到正确的结果。