重写ToString(),实现按顺序显示带有标志的Property和Field的值

目录

文章目录

前言

一、先给Object扩展

二、自定义Attribute:DisplayIndexAttribute

三、重写ToString()的基类

四、测试代码


提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

前言

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,得到正确的结果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值