c# 扩展方法奇思妙用高级篇五:ToString(string format) 扩展

在.Net中,System.Object.ToString()是用得最多的方法之一,ToString()方法在Object类中被定义为virtual,Object类给了它一个默认实现:

1     public virtual string ToString()
2     {
3         return this.GetType().ToString();
4     }

 .Net中原生的class或struct,如int,DateTime等都对它进行重写(override),以让它返回更有价值的值,而不是类型的名称。合理重写的ToString()方法中编程、调试中给我们很大方便。但终究一个类只有一个ToString()方法,不能满足我们多样化的需求,很多类都对ToString()进行了重载。如下:

1     string dateString = DateTime.Now.ToString("yyyy");  //2009
2     string intString = 10.ToString("d4");  //0010

 int、DateTime都实现了ToString(string format)方法,极大方便了我们的使用。

 对于我们自己定义的类型,我们也应该提供一个合理的ToString()重写,如果能够提供再提供一个ToString(string format),就会令我们后期的工作更加简单。试看以下类型: 

复制代码
 1     public class People
 2     {
 3         private List<People> friends = new List<People>();
 4 
 5         public int Id { getset; }
 6         public string Name { getset; }
 7         public DateTime Brithday { getset; }
 8         public People Son { getset; }
 9         public People[] Friends { get { return friends.ToArray(); } }
10 
11         public void AddFriend(People newFriend)
12         {
13             if (friends.Contains(newFriend)) throw new ArgumentNullException("newFriend""该朋友已添加");
14             else friends.Add(newFriend);
15         }
16         public override string ToString()
17         {
18             return string.Format("Id: {0}, Name: {1}", Id, Name);
19         }
20         
21     }
复制代码

 一个简单的类,我们给出一个ToString()重写,返回包含Id和Name两个关键属性的字符串。现在我们需要一个ToString(string format)重写,以满足以下应用:

1     People p = new People { Id = 1, Name = "鹤冲天", Brithday = new DateTime(199099) };
2     string s0 = p.ToString("Name 生日是 Brithday"); //理想输出:鹤冲天 生日是 1990-9-9
3     string s1 = p.ToString("编号为:Id,姓名:Name"); //理想输出:编号为:1,姓名:鹤冲天

 想想怎么实现吧,记住format是可变的,不定使用了什么属性,也不定进行了怎样的组合...

 也许一个类好办,要是我们定义很多类,几十、几百个怎么办?一一实现ToString(string format)会把人累死的。好在我们有扩展方法,我们对object作一扩展ToString(string format),.Net中object是所有的基类,对它扩展后所有的类都会自动拥有了。当然已有ToString(string format)实现的不会,因为原生方法的优先级高,不会被扩展方法覆盖掉。

 来看如何实现吧(我们会一步一步改进,为区分各个版本,分别扩展为ToString1、ToString2...分别对应版本一、版本二...):

复制代码
 1     public static string ToString1(this object obj, string format)
 2     {
 3         Type type = obj.GetType();
 4         PropertyInfo[] properties =  type.GetProperties(
 5             BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance);
 6 
 7         string[] names = properties.Select(p => p.Name).ToArray();
 8         string pattern = string.Join("|", names);
 9 
10         MatchEvaluator evaluator = match =>
11             {
12                 PropertyInfo property = properties.First(p => p.Name == match.Value);
13                 object propertyValue = property.GetValue(obj, null);
14                 if (propertyValue != nullreturn propertyValue.ToString();
15                 else return "";
16             };
17         return Regex.Replace(format, pattern, evaluator);
18     }
复制代码

 3~5行通过反射获取了公有的、实例的Get属性(如果需要静态的或私有的,修改第5行中即可),7~8行动态生成一个正则表达式来匹配format,10~16行是匹配成功后的处理。这里用到反射和正则表达式,如果不熟悉不要紧,先调试运行吧,测试一下前面刚提到的应用:

 第一个和我们理想的有点差距,就是日期上,我们应该给日期加上"yyyy-MM-dd"的格式,这个我们稍后改进,我们现在有一个更大的问题:

 如果我们想输出:“People: Id 1, Name 鹤冲天”,format怎么写呢?写成format="People: Id Id, Name Name",这样没法处理了,format中两个Id、两个Name,哪个是常量,哪个是变量啊?解决这个问题,很多种方法,如使用转义字符,可是属性长了不好写,如format="\B\r\i\t\h\d\a\y Brithday"。我权衡了一下,最后决定采用类似Sql中对字段名的处理方法,在这里就是给变量加上中括号,如下:

1     People p2 = new People { Id = 1, Name = "鹤冲天", Brithday = new DateTime(199099) };
2     string s2 = p1.ToString2("People:Id [Id], Name [Name], Brithday [Brithday]");

 版本二的实现代码如下:

复制代码
 1    public static string ToString2(this object obj, string format)
 2    {
 3        Type type = obj.GetType();
 4        PropertyInfo[] properties = type.GetProperties(
 5            BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance);
 6 
 7        MatchEvaluator evaluator = match =>
 8        {
 9            string propertyName = match.Groups["Name"].Value;
10            PropertyInfo property = properties.FirstOrDefault(p => p.Name == propertyName);
11            if (property != null)
12            {
13                object propertyValue = property.GetValue(obj, null);
14                if (propertyValue != nullreturn propertyValue.ToString();
15                else return "";
16            }
17            else return match.Value;
18        };
19        return Regex.Replace(format, @"\[(?<Name>[^\]]+)\]", evaluator, RegexOptions.Compiled);
20    }
复制代码

 调试执行一下:

 

  与版本一类似,不过这里没有动态构建正则表达式,因为有了中括号,很容易区分常量和变量,所以我们通过“属性名”来找“属性”(对应代码中第10行)。如果某个属性找不到,我们会将这“[Name]”原样返回(对就第17行)。另一种做法是抛出异常,我不建议抛异常,在ToString(string format)是不合乎“常理”的。 

 版本二相对版本一效率有很大提高,主要是因为版本二只使用一个简单的正则表达式:@"\[(?<Name>[^\]]+)\]"。而版本一中的如果被扩展类的属性特别多,动态生成的正则表达式会很长,执行起来也会相对慢。
 

 我们现在来解决两个版本中都存在的时间日期格式问题,把时间日期格式"yyyy-MM-dd"也放入中括号中,测试代码如下:

1     People p3 = new People { Id = 1, Name = "鹤冲天", Brithday = new DateTime(199099) };
2     string s3 = p3.ToString3("People:Id [Id: d4], Name [Name], Brithday [Brithday: yyyy-MM-dd]");

 版本三实现代码:

复制代码
 1     public static string ToString3(this object obj, string format)
 2     {
 3         Type type = obj.GetType();
 4         PropertyInfo[] properties = type.GetProperties(
 5             BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance);
 6 
 7         MatchEvaluator evaluator = match =>
 8         {
 9             string propertyName = match.Groups["Name"].Value;
10             string propertyFormat = match.Groups["Format"].Value;
11 
12             PropertyInfo propertyInfo = properties.FirstOrDefault(p => p.Name == propertyName);
13             if (propertyInfo != null)
14             {
15                 object propertyValue = propertyInfo.GetValue(obj, null);
16                 if (string.IsNullOrEmpty(propertyFormat) == false)
17                     return string.Format("{0:" + propertyFormat + "}", propertyValue);
18                 else return propertyValue.ToString();
19             }
20             else return match.Value;
21         };
22         string pattern = @"\[(?<Name>[^\[\]:]+)(\s*:\s*(?<Format>[^\[\]:]+))?\]";
23         return Regex.Replace(format, pattern, evaluator, RegexOptions.Compiled);
24     }
复制代码

 测试一下,可OK了:

 

  对于简单的值类型属性没问题了,但对于复杂一些类型如,如People的属性Son(Son就是儿子,我一开始写成了Sun),他也是一个People类型,他也有属性的,而且他也可能有Son...

 先看下调用代码吧:

1     People p4 = new People { Id = 1, Name = "鹤冲天", Brithday = new DateTime(199099) };
2     p4.Son = new People { Id = 2, Name = "鹤小天", Brithday = new DateTime(201599) };
3     p4.Son.Son = new People { Id = 3, Name = "鹤微天", Brithday = new DateTime(204099) };
4     string s4 = p4.ToString4("[Name] 的孙子 [Son.Son.Name] 的生日是:[Son.Son.Brithday: yyyy年MM月dd日]。");

 “鹤冲天”也就是我了,有个儿子叫“鹤小天”,“鹤小天”有个儿子,也就是我的孙子“鹤微天”。哈哈,祖孙三代名字都不错吧(过会先把小天、微天这两个名字注册了)!主要看第4行,format是怎么写的。下面是版本四实现代码,由版本三改进而来:

复制代码
 1     public static string ToString4(this object obj, string format)
 2     {
 3         MatchEvaluator evaluator = match =>
 4         {
 5             string[] propertyNames = match.Groups["Name"].Value.Split('.');
 6             string propertyFormat = match.Groups["Format"].Value;
 7 
 8             object propertyValue = obj;
 9             try
10             {
11                 foreach (string propertyName in propertyNames)
12                     propertyValue = propertyValue.GetPropertyValue(propertyName);
13             }
14             catch
15             {
16                 return match.Value;
17             }
18 
19             if (string.IsNullOrEmpty(format) == false)
20                 return string.Format("{0:" + propertyFormat + "}", propertyValue);
21             else return propertyValue.ToString();
22         };
23         string pattern = @"\[(?<Name>[^\[\]:]+)(\s*[:]\s*(?<Format>[^\[\]:]+))?\]";
24         return Regex.Replace(format, pattern, evaluator, RegexOptions.Compiled);
25     }
复制代码

 为了反射获取属性方法,用到了GetPropertyValue扩展如下(版本三的实现用上这个扩展会更简洁)(考虑性能请在此方法加缓存):

复制代码
1     public static object GetPropertyValue(this object obj, string propertyName)
2     {
3         Type type = obj.GetType();
4         PropertyInfo info = type.GetProperty(propertyName);
5         return info.GetValue(obj, null);
6     }
复制代码

 先执行,再分析:

 

 执行正确! 版本四,8~17行用来层层获取属性。也不太复杂,不多作解释了。说明一下,版本四是不完善的,没有做太多处理。

 我们最后再来看一下更复杂的应用,Peoplee有Friends属性,这是一个集合属性,我们想获取朋友的个数,并列出朋友的名字,如下:

1     People p5 = new People { Id = 1, Name = "鹤冲天"};
2     p5.AddFriend(new People { Id = 11, Name = "南霸天" });
3     p5.AddFriend(new People { Id = 12, Name = "日中天" });
4     string s5 = p5.ToString5("[Name] 目前有 [Friends: .Count] 个朋友:[Friends: .Name]。");

 注意,行4中的Count及Name前都加了个小点,表示是将集合进行操作,这个小点是我看着方便自己定义的。再来看实现代码,到版本五了:

复制代码
 1     public static string ToString5(this object obj, string format)
 2     {
 3         MatchEvaluator evaluator = match =>
 4         {
 5             string[] propertyNames = match.Groups["Name"].Value.Split('.');
 6             string propertyFormat = match.Groups["Format"].Value;
 7 
 8             object propertyValue = obj;
 9 
10             try
11             {
12                 foreach (string propertyName in propertyNames)
13                     propertyValue = propertyValue.GetPropertyValue(propertyName);
14             }
15             catch
16             {
17                 return match.Value;
18             }
19 
20             if (string.IsNullOrEmpty(propertyFormat) == false)
21             {
22                 if (propertyFormat.StartsWith("."))
23                 {
24                     string subPropertyName = propertyFormat.Substring(1);
25                     IEnumerable<object> objs = ((IEnumerable)propertyValue).Cast<object>();
26                     if (subPropertyName == "Count")
27                         return objs.Count().ToString();
28                     else
29                     {
30                         string[] subProperties = objs.Select(
31                             o => o.GetPropertyValue(subPropertyName).ToString()).ToArray();
32                         return string.Join("", subProperties);
33                     }
34                 }
35                 else
36                     return string.Format("{0:" + propertyFormat + "}", propertyValue);
37             }
38             else return propertyValue.ToString();
39         };
40         string pattern = @"\[(?<Name>[^\[\]:]+)(\s*[:]\s*(?<Format>[^\[\]:]+))?\]";
41         return Regex.Replace(format, pattern, evaluator, RegexOptions.Compiled);
42     }
复制代码

 执行结果:

 

 比较不可思议吧,下面简单分析一下。行22~行33是对集合进行操作的相关处理,这里只是简单实现了Count,当然也可以实现Min、Max、Sum、Average等等。“.Name”这个表示方法不太好,这里主要是为了展示,大家能明白了就好。 

 

 就写到这里吧,版本六、版本七...后面还很多,当然一个比一个离奇,不再写了。给出五个版本,版本一存在问题,主要看后三个版本,给出多个版本是为满足不同朋友的需要,一般来说版本三足够,对于要求比较高,追求新技术的朋友,我推荐版本四、五。要求更高的,就是没给出的六、七...了。

 ToString(string format)扩展带来便利性的同时,也会带来相应的性能损失,两者很难兼得。

 最后重申下,本系列文章,侧重想法,所给的代码仅供演示、参考,没有考虑性能、异常处理等,如需实际使用,请自行完善。 

转载于:https://www.cnblogs.com/ywsoftware/archive/2013/06/09/3128773.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/462449.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PHP使用CURL抓取页面

cURL的基本原理 curl是利用URL语法在命令行方式下工作的开源文件传输工具&#xff0c;他能够从互联网上获得各种各样的网络资源。简单来说&#xff0c;curl就是抓取页面的升级版。 <?php//1.初始化&#xff0c;创建一个新cURL资源$ch curl_init(); //2.设置URL和相应的选…

Lync Server 2010迁移至Lync Server 2013故障排错 Part3 :内外网共享PPT提示证书问

最近在公司生产环境中部署Lync Server 2013并对公网发布后&#xff0c;发现公网未加域客户端在与内网用户共享PPT时&#xff0c;会报证书错误&#xff0c;如下图所示&#xff1a;但是内网是正确的&#xff0c;经过查询了一下Lync 客户端自身的日志信息后发现&#xff0c;公网客…

用WinInet开发Internet客户端应用指南

一&#xff1a;http://www.vckbase.com/document/viewdoc/?id545 二&#xff1a;http://www.vckbase.com/document/viewdoc/?id546 用WinInet开发Internet客户端应用指南&#xff08;一&#xff09; 编译/NorthTibet 一、概述一个Internet客户端程序的目的是通过Internet…

关于Android错误 View requires API level 14 (current...

2019独角兽企业重金招聘Python工程师标准>>> 问题描述&#xff1a;在界面配置文件main.xml中&#xff0c;可能用上某些控件&#xff08;例如GridLayout&#xff09;遇上下面所说的错误:View requires API level 14(current min is 8), 但将项目clear后&#xff0c;再…

javascript 基础 转

最近面试过一些人, 发现即使经验丰富的开发人员, 对于一些基础的理论和细节也常常会模糊. 写本文是因为就我自己而言第一次学习下面的内容时发现自己确实有所收获和感悟. 其实我们容易忽视的javascript的细节还有更多, 本文仅是冰山一角. 希望大家都能通过本文有所斩获.一. Jav…

2019春第二次课程设计实验报告

2019春第二次课程设计实验报告 一、实验项目名称 用数组实现反弹球消砖块 二、实验项目功能描述 利用键盘按键控制小球方向让小球碰到挡板后反弹上去消除砖块。 三、项目模块结构介绍 四、实现界面展示 五、代码托管链接 https://gitee.com/huangxuannn/events 六、实验总结 上…

nodejs+redis

为什么80%的码农都做不了架构师&#xff1f;>>> redis可以用来管理session http://www.csser.com/board/4f77e6f996ca600f78000936 然后使用的时候redis-server这个服务进程得开着&#xff0c;不然会悲剧的 转载于:https://my.oschina.net/kiloct/blog/137629

FxCAD 实验三 实现对象的【属性】事件

接着实验三&#xff0c;本次实验&#xff0c;实现鼠标右键点击【图形对象时】可以弹出一个【WinForm】属性界面 添加一个WindowsForm 界面&#xff0c;如图。类名字&#xff1a;FormBox 回到FxBox类文件 覆盖 SelectEvent 方法&#xff0c;添加上面的代码 运行程序&#xff0c;…

图像的灰度处理

图像的灰度处理的三种方法&#xff1a; 1.imread的方法将像素值修改为0 2.调用一个RGB转灰度的方法实现灰度转化&#xff08;cv2.COLOR_BGR2GRAY&#xff09; 3.R G B 的均值取灰度值来灰度转化&#xff08;原理&#xff09; 处理结果 如下: 转载于:https://www.cnblogs.com/Ja…

面向对象基础-委托与事件

首先举一个实例&#xff1a;创建一个控制台程序&#xff0c;需求是&#xff1a;有一只猫Tom&#xff0c;有两只老鼠Jerry和Jack&#xff0c;Tom只叫一声“喵&#xff0c;我是Tom”&#xff0c;两只老鼠就说“老猫来了&#xff0c;快跑”&#xff0c;我们来分析一下&#xff1a;…

linux下wget的用法

2019独角兽企业重金招聘Python工程师标准>>> Linux wget是一个下载文件的工具&#xff0c;它用在命令行下。对于Linux用户是必不可少的工具&#xff0c;尤其对于网络管理员&#xff0c;经常要下载一些软件或从远程服务器恢复备份到本地服务器。如果我们使用虚拟主机…

【传智播客】Libevent学习笔记(三):事件循环

目录 00. 目录01. event_base_loop函数02. event_base_dispatch函数03. event_base_loopexit函数04. event_base_loopbreak函数05. event_base_got_exit函数06. event_base_got_break函数07. event_base_dump_events函数08. event_base_dump_events函数09. 废弃的事件循环函数1…

Android的MVC框架

http://www.cnblogs.com/wanghafan/archive/2012/07/20/2600786.html MVC是当前比较流行的框架&#xff0c;随便Google下&#xff0c;就可以发现几乎所有的应用程序开发中都采用了MVC框架&#xff0c;例如&#xff1a;.NET&#xff0c;Java Spring&#xff0c;Java Struts&…

DVWA-SQL注入

SQL注入解题思路 寻找注入点&#xff0c;可以通过web扫描工具实现通过注入点&#xff0c;尝试得到连接数据库的用户名&#xff0c;数据库名称&#xff0c;权限等信息。猜解关键数据库表极其重要字段与内容。通过获得的用户信息寻找后台进行登录。利用后台上传webshell或一句话木…

easyui验证:validatebox

2019独角兽企业重金招聘Python工程师标准>>> $.extend($.fn.validatebox.defaults.rules, { eqTrim : {/* 填写名字前面不能有空格&#xff01; */ validator : function(value,param) { var reg new RegExp(param); var falg!reg.test(value); return falg; }, me…

秒懂机器学习---当机器学习遇上决策树....

秒懂机器学习---当机器学习遇上决策树.... 一、总结 一句话总结&#xff1a; 多多看图&#xff0c;图的直观效果很好&#xff0c;很多时候文字实在表达不清 1、决策树&#xff08;Decision Tree&#xff09;中的各个节点表示什么意思&#xff1f; 每一个树节点表示一个属性上的…

Linux系统目录结构及主要内容

为什么80%的码农都做不了架构师&#xff1f;>>> 文件结构 &#xff1a; /&#xff1a;根目录&#xff0c;所有的目录、文件、设备都在/之下&#xff0c;/就是Linux文件系统的组织者&#xff0c;也是最上级的领导者。 /bin&#xff1a; bin 就是二进制&#xff08;b…

学习《深度学习入门:基于Python的理论与实现》高清中文版PDF+源代码

入门神经网络深度学习&#xff0c;推荐学习《深度学习入门&#xff1a;基于Python的理论与实现》&#xff0c;这本书不来虚的&#xff0c;一上来就是手把手教你一步步搭建出一个神经网络&#xff0c;还能把每一步的出处讲明白。理解神经网络&#xff0c;很容易就能入门。 深度学…

学习《PythonWeb开发实战(董伟明)》中文PDF+源代码

python可以用了进行数据分析&#xff0c;也可以进行Web开发&#xff0c;一般会使用django或者flask等进行开发。 国内介绍python web的书有写的不错的&#xff0c;推荐看看《PythonWeb开发实战》 他的定位是进阶读者&#xff0c;介绍了Python web的生态&#xff0c;有些包介绍得…

周鸿祎:在360新员工入职培训上的讲话

这个是周鸿祎给入司360新员工的一段话&#xff0c;不但适合360&#xff0c;也适合所有的公司员工。特此分享。我想给新入职的同事讲一讲我的期望&#xff0c;再提几个建议。我这个人喜欢说真话&#xff0c;不喜欢说漂亮话&#xff0c;因为漂亮话没用。但说真话&#xff0c;大家…