C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]
前言
本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html 。本文主要学习记录以下内容:
建议13、为类型输出格式化字符串
建议14、正确实现浅拷贝和深拷贝
建议15、使用dynamic来简化反射实现
建议13、为类型输出格式化字符串
有两种方法可以为类型提供格式化的字符串输出。
一种是意识到类型会产生格式化字符串输出,于是让类型继承接口IFormattable。这对类型来说,是一种主动实现的方式,要求开发者可以预见类型在格式化方面的要求。
更多的时候,类型的使用者需为类型自定义格式化器,这就是第二种方法,也是最灵活多变的方法,可以根据需求的变化为类型提供多个格式化器。
下面我们就来看一下这两种方式的实现。
最简单的字符串输出是为类型重写ToString()方法,如果没有为类型重写该方法,默认会调用Ojbect的ToString方法,它会返回当前类型的类型名称。但即使是重写了ToString()方法,提供的字符串输出也是非常单一的,而通过实现IFormattable接口的ToString()方法,可以让类型根据用户的输入而格式化输出。
下面我们来看一个简单的小例子:
public class Person:IFormattable
{
public string IDCode { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
/// <summary>
/// 实现接口Iformattable的方法ToString
/// </summary>
/// <param name="format"></param>
/// <param name="formatProvider"></param>
/// <returns></returns>
public string ToString(string format, IFormatProvider formatProvider)
{
switch (format)
{
case"Ch":
return this.ToString();
case"Eg":
return string.Format("{0}{1}", this.FirstName, this.LastName);
default:
return
this.ToString();
}
}
///重写Object的方法ToString()
public override string ToString()
{
return string.Format("{0}{1}",this.LastName,this.FirstName);
}
}
调用代码如下所示:
static void Main(string[] args)
{
Person person = new Person() { FirstName="Kris",LastName="aehyok"};
Console.WriteLine(person);
Console.WriteLine(person.ToString("Ch",null));
Console.WriteLine(person.ToString("Eg", null));
Console.ReadLine();
}
调用执行结果如下:
下面我们来继续介绍第二实现方式——格式化器。如果类型本身没有提供格式化的功能,那么格式化器就可以派上用场了。格式化器的好处就是可以根据需求的变化,随时增加或者修改它。
接下来我们继续来看另外的一个小例子:
首先定义一个实体类Person:
public class Person
{
public string IDCode { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
一个典型的格式化器应该继承IFormatProvider和ICustomerFormatter,看代码:
public class PersonFomatter:IFormatProvider,ICustomFormatter
{
#region IFormatProvider成员
public object GetFormat(Type formatType)
{
if (formatType == typeof(ICustomFormatter))
{
return this;
}
else
{
return null;
}
}
#endregion #region ICustomFormatter成员
public string Format(string format, object arg, IFormatProvider formatProvider)
{
Person person = arg as Person;
if (person == null)
{
return string.Empty;
}
switch (format)
{
case"Ch":
return string.Format("{0} {1}",person.LastName,person.FirstName);
case"":
return string.Format("{0} {1}",person.FirstName,person.LastName);
case"CHM":
return string.Format("{0} {1}:{2}", person.LastName, person.FirstName, person.IDCode);
default:
return string.Format("{0} {1}", person.LastName, person.FirstName);
}
}
#endregion
}
调用代码如下:
class Program
{
static void Main(string[] args)
{
Person person = new Person() { FirstName="Kris", LastName="aehyok", IDCode="ID000001"};
Console.WriteLine(person.ToString());
PersonFomatter pFomatter = new PersonFomatter();
Console.WriteLine(pFomatter.Format("Ch", person, null));
Console.WriteLine(pFomatter.Format("Eg", person, null));
Console.WriteLine(pFomatter.Format("CHM", person, null));
Console.ReadLine();
}
}
调用执行结果如下:
其实还有另外一种变通的形式,就是将这两种方式合并一起使用的过程,下面来看一下具体的实现代码:
public class Person:IFormattable
{
public string IDCode { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
/// <summary>
/// 实现接口Iformattable的方法ToString
/// </summary>
/// <param name="format"></param>
/// <param name="formatProvider"></param>
/// <returns></returns>
public string ToString(string format, IFormatProvider formatProvider)
{
switch (format)
{
case"Ch":
return this.ToString();
case"Eg":
return string.Format("{0}{1}", this.FirstName, this.LastName);
default:
//return this.ToString();
ICustomFormatter customerFormatter = formatProvider as ICustomFormatter;
if (formatProvider == null)
{
return this.ToString();
}
return customerFormatter.Format(format, this, null);
}
}
///重写Object的方法ToString()
public override string ToString()
{
return string.Format("{0}{1}",this.LastName,this.FirstName);
}
}
PersonFomatter自定义格式化器的代码并没有发生任何的改变。
调用代码如下:
static void Main(string[] args)
{
Person person = new Person() { FirstName="Kris", LastName="aehyok", IDCode="ID000001"};
Console.WriteLine(person.ToString());
PersonFomatter pFomatter = new PersonFomatter();
Console.WriteLine(pFomatter.Format("Ch", person, null));
Console.WriteLine(pFomatter.Format("Eg", person, null));
Console.WriteLine(pFomatter.Format("CHM", person, null)); Console.WriteLine(person.ToString("Ch",pFomatter));
Console.WriteLine(person.ToString("Eg", pFomatter));
Console.WriteLine(person.ToString("CHM", pFomatter));
Console.ReadLine();
}
调用执行结果如下所示:
建议14、正确实现浅拷贝和深拷贝
为对象创建副本的技术成为拷贝(也叫克隆)。我们将拷贝分为浅拷贝和深拷贝。
浅拷贝 将对象中的所有字段复制到新的对象(副本)中。其中,值类型字段的值被复制到副本中后,在副本中的修改不会影响到源对象对应的值。 而引用类型的字段被复制到副本中的是引用类型的引用,而不是引用的对象,在副本中对引用类型的字段值做修改会影响到源对象本身。
深拷贝 同样,将对象中的所有字段复制到新的对象中。不过无论是对象的值类型字段,还是引用类型字段,都会被重新创建并赋值,对于副本的修改,不会影响到源对象本身。
无论是浅拷贝还是深拷贝,微软都建议用类型继承ICloneable接口的方式明确告诉调用者:该类型可以被拷贝。当然,ICloneable接口只提供了一个声明为Clone的方法,我们可根据需求在Clone方法内实现浅拷贝或深拷贝。一个简答的浅拷贝的实现代码如下所示:
首先定义实体类:
public class Employee:ICloneable
{
public string IDCode { get; set; }
public int Age { get; set; }
public Department Department { get; set; } #region OCloneable成员
public object Clone()
{
return this.MemberwiseClone();
}
#endregion
} public class Department
{
public string Name{get;set;}
public override string ToString()
{
return this.Name;
}
}
然后进行调用代码如下:
static void Main(string[] args)
{
Employee Niki = new Employee()
{
IDCode = "IDaehyok",
Age = 25,
Department = new Department() { Name="Depart1" }
};
Employee Kris = Niki.Clone() as Employee;
Console.WriteLine(string.Format("IDCode:{0}\tAge:{1}\tDepartment:{2}", Kris.IDCode, Kris.Age, Kris.Department));
///开始改变Niki的值
Niki.IDCode = "IDNiki";
Niki.Age = 23;
Niki.Department.Name = "Depart2";
Console.WriteLine(string.Format("IDCode:{0}\tAge:{1}\tDepartment:{2}", Kris.IDCode, Kris.Age, Kris.Department));
Console.ReadLine();
}
调用执行结果如下
注意到Employee的IDCode属string类型。理论上string类型是引用类型,但是由于该引用类型的特殊性(无论是实际还是语义),Object.MemberwiseClone方法仍旧为其创建了副本。也就是说,在浅拷贝过程,我们应该将字符串看成是值类型。Employee的Department属性是一个引用类型,所以,如果改变了源对象Niki中的值,那么副本Kris中的值也会随之一起变动。
Employee的深拷贝有多种实现方法,最简单的方式是手动的对字段进行逐个的赋值。但是这种方法容易出错,也就是说,如果类型的字段发生变化或有增减,那么该拷贝方法也要发生相应的变化,所以,建议使用序列化的形式来进行深拷贝。Employee深拷贝的一种实现方式如下:
[Serializable]
public class Employee:ICloneable
{
public string IDCode { get; set; }
public int Age { get; set; }
public Department Department { get; set; } #region OCloneable成员
public object Clone()
{
using (Stream objectstream = new MemoryStream())
{
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(objectstream, this);
objectstream.Seek(0, SeekOrigin.Begin);
return formatter.Deserialize(objectstream) as Employee;
}
}
#endregion
} [Serializable]
public class Department
{
public string Name{get;set;}
public override string ToString()
{
return this.Name;
}
}
调用方法如下所示:
Employee Niki = new Employee()
{
IDCode = "IDaehyok",
Age = 25,
Department = new Department() { Name="Depart1" }
};
Employee Kris = Niki.Clone() as Employee;
Console.WriteLine(string.Format("IDCode:{0}\tAge:{1}\tDepartment:{2}", Kris.IDCode, Kris.Age, Kris.Department));
///开始改变Niki的值
Niki.IDCode = "IDNiki";
Niki.Age = 23;
Niki.Department.Name = "Depart2";
Console.WriteLine(string.Format("IDCode:{0}\tAge:{1}\tDepartment:{2}", Kris.IDCode, Kris.Age, Kris.Department));
Console.ReadLine();
最终代码调用结果如下
可以发现再次改变Niki的值,不会对副本Kris产生影响。
由于接口ICloneable,只有一个模棱两可的方法,所以,如果要在一个类中进行浅拷贝和深拷贝,只能由我们额外的实现两个方法。声明为DeepClone和Shallow。那么最终代码如下所示:
[Serializable]
public class Employee:ICloneable
{
public string IDCode { get; set; }
public int Age { get; set; }
public Department Department { get; set; } #region OCloneable成员
public object Clone()
{
return this.MemberwiseClone();
}
#endregion public Employee DeeptClone()
{
using (Stream objectstream = new MemoryStream())
{
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(objectstream, this);
objectstream.Seek(0, SeekOrigin.Begin);
return formatter.Deserialize(objectstream) as Employee;
}
} public Employee Shallow()
{
return Clone() as Employee;
}
}
建议15、使用dynamic来简化反射实现
Dynamic是Framework4.0的新特性。dynamic的出现让C#具有了弱类型的特性。编译器在编译的时候不再对类型进行检查,编译器默认dynamic对象支持开发者想要的任何类型。如果运行时不包含指定的特性,运行时程序会抛出一个RuntimeBinderException异常。
下面我们先来看一个简单的例子
public class DynamicSample
{
public string Name { get; set; }
public int Add(int a, int b)
{
return a + b;
}
}
现在我们想调用上面实体类的Add方法,实现方式可以是这样的:
DynamicSample dynamicSample = new DynamicSample();
var addMethod = typeof(DynamicSample).GetMethod("Add");
int re = (int)addMethod.Invoke(dynamicSample, new object[] { 1, 2 });
Console.WriteLine(re);
下面我们再通过使用dynamic来实现一下:
dynamic dynamic = new DynamicSample();
int re2 = dynamic.Add(1, 2);
Console.WriteLine(re2);
可以发现dynamic的实现方式很简洁,而且性能也有所提升,当然上面一次的调用我们是看不出什么效果的,假如上面的代码我们进行调用了10000000次。
int times = 10000000;
////第一种调用方式
DynamicSample reflectSample = new DynamicSample();
var addMethod = typeof(DynamicSample).GetMethod("Add");
Stopwatch watch1 = Stopwatch.StartNew();///用于测试运行时间
for (var i = 0; i < times; i++)
{
addMethod.Invoke(reflectSample, new object[] { 1, 2 });
}
Console.WriteLine(string.Format("普通方法反射耗时:{0} 毫秒", watch1.ElapsedMilliseconds)); ////第二种调用方式
dynamic dynamicSample = new DynamicSample();
Stopwatch watch2 = Stopwatch.StartNew();
for (int i = 0; i < times; i++)
{
dynamicSample.Add(1, 2);
}
Console.WriteLine(string.Format("dynamic方式耗时:{0} 毫秒", watch2.ElapsedMilliseconds)); ////第三种调用方式
DynamicSample reflectSampleBetter = new DynamicSample();
var addMethod2 = typeof(DynamicSample).GetMethod("Add");
var delg = (Func<DynamicSample, int, int, int>)Delegate.CreateDelegate(typeof(Func<DynamicSample, int, int, int>), addMethod2);
Stopwatch watch3 = Stopwatch.StartNew();
for (var i = 0; i < times; i++)
{
delg(reflectSampleBetter, 1, 2);
}
Console.WriteLine(string.Format("优化的反射耗时:{0} 毫秒", watch3.ElapsedMilliseconds));
Console.ReadLine();
调用执行后的结果为
现在可以看出很明显的区别,普通方法调用发射执行效率远远的低于使用dynamic。第三种方式是我们优化了发射之后的执行时间,比使用dynamic也有所提升,但是并不是特别明显,虽然带来了性能的提升,不过却牺牲了代码的整洁性。这种实现方式在我看来是得不偿失的。所以建议大家使用dynamic来优化发射。
C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]的更多相关文章
- C#程序编写高质量代码改善的157个建议[正确操作字符串、使用默认转型方法、却别对待强制转换与as和is]
前言 本文主要来学习记录前三个建议. 建议1.正确操作字符串 建议2.使用默认转型方法 建议3.区别对待强制转换与as和is 其中有很多需要理解的东西,有些地方可能理解的不太到位,还望指正. 建议1. ...
- C#程序编写高质量代码改善的157个建议【20-22】[泛型集合、选择集合、集合的安全]
建议20.使用泛型集合来替代非泛型集合 http://www.cnblogs.com/aehyok/p/3384637.html 这里有一篇文章,是我之前专门来介绍泛型的.我们应尽量的使用泛型集合.因 ...
- C#程序编写高质量代码改善的157个建议【16-19】[动态数组、循环遍历、对象集合初始化]
前言 软件开发过程中,不可避免会用到集合,C#中的集合表现为数组和若干集合类.不管是数组还是集合类,它们都有各自的优缺点.如何使用好集合是我们在开发过程中必须掌握的技巧.不要小看这些技巧,一旦在开 ...
- C#程序编写高质量代码改善的157个建议【10-12】[创建对象时需要考虑是否实现比较器、区别对待==和Equals]
前言 建议10.创建对象时需要考虑是否实现比较器 建议11.区别对待==和Equals 建议12.重写Equals时也要重写GetHashCode 建议10.创建对象时需要考虑是否实现比较器 有对象的 ...
- C#程序编写高质量代码改善的157个建议【4-9】[TryParse比Parse、使用int?来确保值类型也可以为null、readonly和const、0值设为枚举的默认值、避免给枚举类型的元素提供显式的值、习惯重载运算符]
建议4.TryParse比Parse好 如果注意观察,除string之外的所有的基元类型.会发现它们都有两个将字符串转换为自身类型的方法:Parse和TryParse.以类型double为例. 两者最 ...
- 编写高质量代码改善C#程序的157个建议——建议13: 为类型输出格式化字符串
建议13: 为类型输出格式化字符串 有两种方法可以为类型提供格式化的字符串输出.一种是意识到类型会产生格式化字符串输出,于是让类型继承接口IFormattable.这对类型来 说,是一种主动实现的方式 ...
- 编写高质量代码改善C#程序的157个建议——导航开篇
前言 由于最近工作重心的转移,原来和几个同事一起开发的项目也已经上线了,而新项目就是在现有的项目基础上进行优化延伸扩展.打个比方,现在已经上线的项目行政案件的Web管理网站(代码还没那么多相比较即将要 ...
- 编写高质量代码改善C#程序的157个建议读书笔记【11-20】
章节索引 建议11:区别对待 == 和Equals 建议12:重写Equals也要重写GetHashCode 建议13:为类型输出格式化字符串 建议14:正确实现浅拷贝和深拷贝 建议15:使用dyna ...
- 编写高质量代码改善C#程序的157个建议[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]
前言 本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html .本文主要学习记录以下内容: 建议13.为类型输出格式化字符串 建议14.正确实现浅拷贝和深 ...
随机推荐
- [07/18NOIP模拟测试5]超级树
鬼能想到的dp定义:dp[i][j]表示在一棵i级超级树中,有j条路径同时存在且这j条路径没有公共点时,可能的情况数 刚开始我也没看懂,所以举个例子 如一个2级的超级树,父节点为1,左右儿子为2,3 ...
- Vue 2.x 3.x 配置项目开发环境跟线上环境
先找到package.json (这是nuxt版的vue 可能会跟一般vue不一样 当然总体上差不多的) "scripts": { "dev": " ...
- LINUX 内核移植以及网卡驱动添加
我用的板子是sama5d3xek,原来板子内核是linux-at91-3.13,升级使用linux-at91-4.10 首先去官网下载一个linux—at91-4.10压缩包,然后在ubuntu里解压 ...
- 如何把链表以k个结点为一组进行翻转
[MT笔试题] 题目描述: K 链表翻转是指把每K个相邻的结点看成一组进行翻转,如果剩余结点不足 K 个,则保持不变.假设给定链表 1 -> 2 -> 3 -> 4 -> 5 ...
- systemd 服务管理编写
1.编辑服务管理脚本 $ cat /lib/systemd/system/kafka.service [Unit] Description=Kafka Server Documentation=htt ...
- JSON《===》JavaBean的相互转换
1.JSON的作用和好处 在JavaWeb项目中前后端直接的交互,接口之间的对接等等,基本离不开JSON. JSON: 全称JavaScript Object Notation(JavaScript ...
- 多线程之美1一volatile
目录 一.java内存模型 1.1.抽象结构图 1.2.概念介绍 二.volatile详解 2.1.概念 2.2.保证内存可见性 2.3.不保证原子性 2.4.有序性 一.java内存模型 1.1.抽 ...
- 微擎修改 icon.jpg 后项目主页未变
微擎修改 icon.jpg 后项目主页Logo未变 产生原因: 设置了自定义图标,但系统未找到该图标,就选择使用默认的起始图标 解决办法: 在项目根目录位置上传一个图标名为 icon-custom.j ...
- nyoj 991 Registration system (map)
Registration system 时间限制:1000 ms | 内存限制:65535 KB 难度:2 描述 A new e-mail service "Berlandesk&q ...
- nyoj 116 士兵杀敌(二)(线段树、单点更新)
士兵杀敌(二) 时间限制:1000 ms | 内存限制:65535 KB 难度:5 描述 南将军手下有N个士兵,分别编号1到N,这些士兵的杀敌数都是已知的. 小工是南将军手下的军师,南将军经常 ...