前言

  本文已更新至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 = ,
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 = ;
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(, 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 = ,
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 = ;
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(, 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[] { , });
Console.WriteLine(re);

下面我们再通过使用dynamic来实现一下:

            dynamic dynamic = new DynamicSample();
int re2 = dynamic.Add(, );
Console.WriteLine(re2);

可以发现dynamic的实现方式很简洁,而且性能也有所提升,当然上面一次的调用我们是看不出什么效果的,假如上面的代码我们进行调用了10000000次。

            int times = ;
////第一种调用方式
DynamicSample reflectSample = new DynamicSample();
var addMethod = typeof(DynamicSample).GetMethod("Add");
Stopwatch watch1 = Stopwatch.StartNew();///用于测试运行时间
for (var i = ; i < times; i++)
{
addMethod.Invoke(reflectSample, new object[] { , });
}
Console.WriteLine(string.Format("普通方法反射耗时:{0} 毫秒", watch1.ElapsedMilliseconds)); ////第二种调用方式
dynamic dynamicSample = new DynamicSample();
Stopwatch watch2 = Stopwatch.StartNew();
for (int i = ; i < times; i++)
{
dynamicSample.Add(, );
}
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 = ; i < times; i++)
{
delg(reflectSampleBetter, , );
}
Console.WriteLine(string.Format("优化的反射耗时:{0} 毫秒", watch3.ElapsedMilliseconds));
Console.ReadLine();

调用执行后的结果为

现在可以看出很明显的区别,普通方法调用发射执行效率远远的低于使用dynamic。第三种方式是我们优化了发射之后的执行时间,比使用dynamic也有所提升,但是并不是特别明显,虽然带来了性能的提升,不过却牺牲了代码的整洁性。这种实现方式在我看来是得不偿失的。所以建议大家使用dynamic来优化发射。

到这里为止,第一章的内容暂时已经整理完毕,感觉自己学了不少知识,继续加油,接下来继续进行第二章集合和LINQ的学习。

编写高质量代码改善C#程序的157个建议[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]的更多相关文章

  1. 编写高质量代码改善C#程序的157个建议[匿名类型、Lambda、延迟求值和主动求值]

    前言 从.NET3.0开始,C#开始一直支持一个新特性:匿名类型.匿名类型由var.赋值运算符和一个非空初始值(或以new开头的初始化项)组成.匿名类型有如下基本特性: 1.既支持简单类型也支持复杂类 ...

  2. 编写高质量代码改善C#程序的157个建议[1-3]

    原文:编写高质量代码改善C#程序的157个建议[1-3] 前言 本文主要来学习记录前三个建议. 建议1.正确操作字符串 建议2.使用默认转型方法 建议3.区别对待强制转换与as和is 其中有很多需要理 ...

  3. 读书--编写高质量代码 改善C#程序的157个建议

    最近读了陆敏技写的一本书<<编写高质量代码  改善C#程序的157个建议>>书写的很好.我还看了他的博客http://www.cnblogs.com/luminji . 前面部 ...

  4. 编写高质量代码改善C#程序的157个建议——建议157:从写第一个界面开始,就进行自动化测试

    建议157:从写第一个界面开始,就进行自动化测试 如果说单元测试是白盒测试,那么自动化测试就是黑盒测试.黑盒测试要求捕捉界面上的控件句柄,并对其进行编码,以达到模拟人工操作的目的.具体的自动化测试请学 ...

  5. 编写高质量代码改善C#程序的157个建议——建议156:利用特性为应用程序提供多个版本

    建议156:利用特性为应用程序提供多个版本 基于如下理由,需要为应用程序提供多个版本: 应用程序有体验版和完整功能版. 应用程序在迭代过程中需要屏蔽一些不成熟的功能. 假设我们的应用程序共有两类功能: ...

  6. 编写高质量代码改善C#程序的157个建议——建议155:随生产代码一起提交单元测试代码

    建议155:随生产代码一起提交单元测试代码 首先提出一个问题:我们害怕修改代码吗?是否曾经无数次面对乱糟糟的代码,下决心进行重构,然后在一个月后的某个周一,却收到来自测试版的报告:新的版本,没有之前的 ...

  7. 编写高质量代码改善C#程序的157个建议——建议154:不要过度设计,在敏捷中体会重构的乐趣

    建议154:不要过度设计,在敏捷中体会重构的乐趣 有时候,我们不得不随时更改软件的设计: 如果项目是针对某个大型机构的,不同级别的软件使用者,会提出不同的需求,或者随着关键岗位人员的更替,需求也会随个 ...

  8. 编写高质量代码改善C#程序的157个建议——建议153:若抛出异常,则必须要注释

    建议153:若抛出异常,则必须要注释 有一种必须加注释的场景,即使异常.如果API抛出异常,则必须给出注释.调用者必须通过注释才能知道如何处理那些专有的异常.通常,即便良好的命名也不可能告诉我们方法会 ...

  9. 编写高质量代码改善C#程序的157个建议——建议152:最少,甚至是不要注释

    建议152:最少,甚至是不要注释 以往,我们在代码中不写上几行注释,就会被认为是钟不负责任的态度.现在,这种观点正在改变.试想,如果我们所有的命名全部采用有意义的单词或词组,注释还有多少存在的价值. ...

随机推荐

  1. DimDate populate data

    日期维度 任何一个数据仓库都应该有一个日期维度. 因为很少有不需要通过日期维度看数据的情况存在. 日期维度的好处是,你可以通过他连接各个事实表,然后在报表端传送报表参数的时候, 直接自动过滤日期维度的 ...

  2. 项目回顾3-再谈图片上传-FormData+ajax上传

    上次在纠结图片上传用base64还是form表单,现在感觉好蠢,因为又开辟了第三条道路. 其实也根本用不到form 只需要一个上传文件的input就好了 <input id="file ...

  3. PHP中的include和require

    1.include语句 使用include语句可以告诉PHP提取特定的文件,并载入它的全部内容 <?php inlude "fileinfo.php"; //此处添加其他代码 ...

  4. 2014-2015 Codeforces Trainings Season 2 Episode 7 G Gophers --线段树

    题意: 有n个地鼠,m个CD碟,每个CD碟有一个影响范围,范围内的地鼠都会被吵到,每次有一个操作就是移动CD碟,然后求每次被影响的地鼠有多少只. 解法: 线段树做.我们只关注地鼠有没有被吵到就可以了, ...

  5. POJ 1845 Sumdiv 【逆元】

    题意:求A^B的所有因子之和 很容易知道,先把分解得到,那么得到,那么 的所有因子和的表达式如下 第一种做法是分治求等比数列的和  用递归二分求等比数列1+pi+pi^2+pi^3+...+pi^n: ...

  6. POJ 3481Double Queue Splay

    #include<stdio.h> #include<string.h> ; ],data[N],id[N],fa[N],size,root; void Rotate(int ...

  7. uGUI练习(二) Animate UI

    练习目标 通过Animation录制UI动画 一.步骤 1.创建一个Panel,下面再创建两个子Panel 2.修改Canvas的 Render Mode为Screen Space-Camer 3.为 ...

  8. Daikon Forge GUI 制作图集和字体集

    Daikon Forge GUI 制作UI面板 在上次的学习中制作了一个简单的面板,下面来学习制作图集以及字体. 1.DF-GUI 图集(Atlas)制作 操作步骤 选中UI Root根节点,在Sce ...

  9. MongoDB学习(三)数据导入导出及备份恢复

    这几天想着公司要用MongoDB,自然就要用到数据导入导出,就自己学习了一下. 在Mongo学习(二)中就讲到了在bin目录下有一些工具,本篇就是使用这些工具进行数据的导入导出及备份恢复. 注意:以下 ...

  10. java 14 -6 BigInteger和BigDecimal

    BigInteger:可以让超过Integer范围内的数据进行运算 构造方法: BigInteger(String val) import java.math.BigInteger; public c ...