博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
c# 扩展方法奇思妙用高级篇五:ToString(string format) 扩展
阅读量:6720 次
发布时间:2019-06-25

本文共 9196 字,大约阅读时间需要 30 分钟。

在.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 { 
get
set
; }
 6 
        
public
 
string
 Name { 
get
set
; }
 7 
        
public
 DateTime Brithday { 
get
set
; }
 8 
        
public
 People Son { 
get
set
; }
 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(
1990
9
9
) };
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 
!=
 
null
return
 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(
1990
9
9
)
 };
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 
!=
 
null
return
 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(
1990
9
9
) };
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(
1990
9
9
) };
2 
    p4.Son 
=
 
new
 People { Id 
=
 
2
, Name 
=
 
"
鹤小天
"
, Brithday 
=
 
new
 DateTime(
2015
9
9
) };
3 
    p4.Son.Son 
=
 
new
 People { Id 
=
 
3
, Name 
=
 
"
鹤微天
"
, Brithday 
=
 
new
 DateTime(
2040
9
9
) };
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

你可能感兴趣的文章
android:layout_gravity 和 android:gravity 的区别
查看>>
嵌入式C题
查看>>
maven学习笔记
查看>>
说说Java线程池
查看>>
Linux挂载命令mount用法及参数详解
查看>>
Nginx 动静分离
查看>>
MySQL如何实现数组功能
查看>>
Android第四十七期 - WheelView沉浸式菜单栏
查看>>
Spring Boot--模板从JSP到Freemarker的若干问题
查看>>
Java内存模型的探究
查看>>
CentOS6.5 从源码编译安装 GCC-4.9.1 全程实录《第二部分:编译,安装,测试》
查看>>
反查bash历史记录--用Enki学Linux系列(16)
查看>>
Stateful firewall
查看>>
Redis 常见的客户端工具
查看>>
Linux Svn 安装
查看>>
我的友情链接
查看>>
Tornado 多进程 & 异步
查看>>
Mysql left join,right join,inner join的效率比较
查看>>
SpringMVC的返回视图几种方式
查看>>
lvs+keepalived实现实时监控节点健康状态,并根据算法接管资源
查看>>