前言

  本文主要来学习记录前三个建议。

  建议1、正确操作字符串

  建议2、使用默认转型方法

  建议3、区别对待强制转换与as和is

其中有很多需要理解的东西,有些地方可能理解的不太到位,还望指正。

建议1、正确操作字符串

  字符串应该是所有编程语言中使用最频繁的一种基础数据类型。如果使用不慎,我们就会为一次字符串的操作所带来的额外性能开销而付出代价。本条建议将从两个方面来探讨如何规避这类性能开销:

  1、确保尽量少的装箱

  2、避免分配额外的内存空间

先来介绍第一个方面,请看下面的两行代码:

String str1="str1"+9;
String str2="str2"+9.ToString();

从IL代码可以得知,第一行代码在运行时完成一次装箱的行为,而第二行代码中并没有发生装箱的行为,它实际调用的是整型的ToString()方法,效率要比装箱高。所以,在使用其他值引用类型到字符串的转换并完成拼接时,应当避免使用操作符“+”来完成,而应该使用值引用类型提供的ToString()方法。

第二方面,避免分配额外的内存空间。对CLR来说,string对象(字符串对象)是个很特殊的对象,它一旦被赋值就不可改变。在运行时调用System.String类中的任何方法或进行任何运算(如“=”赋值、“+”拼接等),都会在内存中创建一个新的字符串对象,这也意味着要为该新对象分配新的内存空间。像下面的代码就会带来运行时的额外开销。

private static void NewMethod1()
{
string s1="abc";
s1="123"+s1+"456"; ////以上两行代码创建了3个字符串对象对象,并执行了一次string.Contact方法
} private static void NewMethod2()
{
string re=9+"456"; ////该方法发生了一次装箱,并调用一次string.Contact方法
}

关于装箱拆箱的问题大家可以查看我之前的文章http://www.cnblogs.com/aehyok/p/3504449.html

而以下代码,字符串不会在运行时进行拼接,而是会在编译时直接生成一个字符串。

private static void NewMethod3()
{
string re2="123"+"abc"+"456"; ///该代码等效于///string re2="123abc456";
} private static void NewMethod4()
{
const string a="t";
string re="abc"+a; ///因为a是一个常量,所以该代码等效于string=re="abc"+"t"; 最终等效于string re="abct";
}

由于使用System.String类会在某些场合带来明显的性能损耗,所以微软另外提供了一个类型StringBuilder来弥补String的不足。

StringBuilder并不会重新创建一个string对象,它的效率源于预先以非托管的方式分配内存。如果StringBuilder没有先定义长度,则默认分配的长度为16。当StringBuilder字符串长度小于等于16时,StringBuilder不会重新分配内存;当StringBuilder字符长度大于16小于32时,StringBuilder又会重新分配内存,使之成为16的倍数。在上面的代码中,如果预先判断字符串的长度将大于16,则可以为其设定一个更加合适的长度(如32)。StringBuilder重新分配内存时是按照上次容量加倍进行分配的。当然,我们需要注意,StringBuilder指定的长度要合适,太小了,需要频繁分配内存,太大了,浪费空间。

查看以下代码,比较下面两种字符串拼接方式,哪种效率更高:

        private static void NewMethod1()
{
string a = "t";
a += "e";
a += "s";
a += "t";
} private static void NewMethod2()
{
string a = "t";
string b = "e";
string c = "s";
string d = "t";
string result = a + b + c + d;
}

  结果可以得知:两者的效率都不高。不要以为前者比后者创建的字符串对象更少,事实上,两者创建的字符串对象相等,且前者进行了3次string.Contact方法调用,比后者还多了两次。

  要完成这样的运行时字符串拼接(注意:是运行时),更佳的做法是使用StringBuilder类型,代码如下所示:

        public static void NewMethod()
{
////定义了四个变量
string a = "t";
string b = "e";
string c = "s";
string d = "t";
StringBuilder sb = new StringBuilder(a);
sb.Append(b);
sb.Append(c);
sb.Append(d); ///提示是运行时,所以没有使用以下代码
//StringBuilder sb = new StringBuilder("t");
//sb.Append("e");
//sb.Append("s");
//sb.Append("t");
//string result = sb.ToString();
}

微软还提供了另外一个方法来简化这种操作,即使用string.Format方法。string.Format方法在内部使用StringBuilder进行字符串的格式化,代码如下所示:

        public static void NewMethod4()
{
string a = "t";
string b = "e";
string c = "s";
string d = "t";
string result = string.Format("{0}{1}{2}{3}", a, b, c, d);
}

对于String和StringBuilder的简单介绍也可以参考我之前的一篇文章http://www.cnblogs.com/aehyok/p/3505000.html

建议2、使用默认转型方法

1、使用类型的转换运算符,其实就是使用类型内部的一方方法(即函数)。转换运算符分为两类:隐式转换和显式转换(强制转换)。基元类型普遍都提供了转换运算符。

所谓“基元类型”,是指编译器直接支持的数据类型。基元类型包括:sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、bool、decimal、object、string。

            int i = 0;
float j = 0;
j = i; ///int 到float存在一个隐式转换
i = (int)j; ///float到int必须存在一个显式转换

用户自定义的类型也可以通过重载转换运算符的方式提供这一类转换:

    public class Ip
{
IPAddress value;
public Ip(string ip)
{
value = IPAddress.Parse(ip);
} //重载转换运算符,implicit 关键字用于声明隐式的用户定义类型转换运算符。
public static implicit operator Ip(string ip)
{
Ip iptemp = new Ip(ip);
return iptemp;
} //重写ToString方法
public override string ToString()
{
return value.ToString();
}
}
class Program
{
public static void Main(string[] args)
{
Ip ip = "192.168.1.1"; //通过Ip类的重载转换运算符,实现字符串到Ip类型的隐式转换
Console.WriteLine(ip.ToString());
Console.ReadLine();
}
}

提供的就是字符串到类型Ip之间的隐式转换。

2、使用类型内置的Parse、TryParse,或者如ToString、ToDouble、ToDateTime等方法

比如从string转换为int,因为其经常发生,所以int本身就提供了Parse和TryParse方法。一般情况下,如果要对某类型进行转换操作,建议先查阅该类型的API文档。

3、使用帮助类提供的方法

可以使用System.Convert类、System.BitConverter类来进行类型的转换。

System.Convert提供了将一个基元类型转换为其他基元类型的方法,如ToChar、ToBoolean方法等。值得注意的是,System.Convert还支持将任何自定义类型转换为任何基元类型,只要自定义类型继承了IConvertible接口就可以。如上文中的IP类,如果将Ip转换为string,除了重写Object的ToString方法外,还可以实现IConvertible的ToString()方法

继承IConvertible接口必须同时实现其他转型方法,如上文的ToBoolean、ToByte,如果不支持此类转型,则应该抛出一个InvalidCastException,而不是一个NotImplementedException。

4、使用CLR支持的转型

CLR支持的转型,即上溯转型和下溯转型。这个概念首先是在Java中提出来的,实际上就是基类和子类之间的相互转换。

就比如: 动作Animal类、Dog类继承Animal类、Cat类也继承自Amimal类。在进行子类向基类转型的时候支持隐式转换,如Dog显然就是一个Animal;而当Animal转型为Dog的时候,必须是显式转换,因为Animal还可能是一个Cat。

            Animal animal = new Animal();
Dog dog = new Dog();
animal = dog; /////隐式转换,因为Dog就是Animal
///dog=animal; ////编译不通过
dog = (dog)animal; /////必须存在一个显式转换

建议3、区别对待强制转换与as和is

首先来看一个简单的实例

            FirstType firstType = new FirstType();
SecondType secondType = new SecondType();
secondType = (SecondType)firstType;

从上面的三行代码可以看出,类似上面的应该就是强制转换。

首先需要明确强制转换可能意味这两件不同的事情:

1、FirstType和SecondType彼此依靠转换操作来完成两个类型之间的转换。

2、FirstType是SecondType的基类。

类型之间如果存在强制转换,那么它们之间的关系要么是第一种,要么是第二种。不可能同时是继承的关系,又提供了转型符。

针对第一种情况:

    public class FirstType
{
public string Name { get; set; }
} public class SecondType
{
public string Name { get; set; } public static explicit operator SecondType(FirstType firstType)
{
SecondType secondType = new SecondType() { Name = "转型自:" + firstType.Name };
return secondType;
}
}
class Program
{
static void Main(string[] args)
{
FirstType firstType = new FirstType() { Name="First Type"};
SecondType secondType = (SecondType)firstType; ///此转换是成功的
secondType = firstType as SecondType; ///编译不通过
Console.ReadLine();
}
}

这里上面也有添加注释,通过强制转换是可以转换成功的,但是使用as运算符是不成功的编译就不通过。

这里就是通过转换符进行处理的结果。

接下来我们再在Program类中添加一个方法

        static void DoWithSomeType(object obj)
{
///编译器首先判断的是,SeondType和ojbect之间有没有继承关系。
///因为在C#中,所有的类型都是继承自object的,所以这里编译没有什么问题。
///但编译器会自动产生代码来检查obj在运行时是不是SecondType,这样就绕过了操作转换符,导致转换失败。
SecondType secondType = (SecondType)obj;
}

如注释所说的,编译通过执行报错的问题。

如果类型之间都上溯到了某个共同的基类,那么根据此基类进行的转换(即基类转型为子类本身),应该使用as。子类与子类之间的转换,则应该提供转换操作符,以便进行强制转换。

现在可以如上方法改写为

        static void DoWithSomeType(object obj)
{
SecondType secondType = obj as SecondType;
}

保证编译执行都不会报错。as操作符永远不会抛出异常,如果类型不匹配(被转换对象的运行时类型既不是所转换的目标类型,也不是其派生类型),或者转型的源对象为null,那么转型之后的值也为null。改造前的DoWithSomeType方法会因为引发异常带来效率问题,而使用as后,就可以完美的避免这种问题。

现在来看第二种情况,即FirstType是SecondType的基类。这种情况下,既可以使用强制转型又可以使用as操作符。

    public class FirstType
{
public string Name { get; set; }
} public class SecondType : FirstType
{
}
class Program
{
static void Main(string[] args)
{
SecondType secondType = new SecondType() { Name="aehyok"};
FirstType firstType = (FirstType)secondType;
firstType = secondType as FirstType;
Console.ReadLine();
}
}

但是,即使可以使用强制转型,从效率的角度来看,也建议大家使用as进行转型。

下面再来看一下is操作符

        static void DoWithSomeType(object obj)
{
if (obj is SecondType)
{
SecondType secondType = obj as SecondType;
}
}

这个版本的效率显然没有上一个版本的效率高。因为当前这个版本进行了两次类型检测。但是,as操作符有个问题,就是它不能操作基元类型。如果涉及到基元类型的算法,那么就要使用is进行判断之后再进行转型的操作,以避免转型失败。

C#程序编写高质量代码改善的157个建议[正确操作字符串、使用默认转型方法、却别对待强制转换与as和is]的更多相关文章

  1. C#程序编写高质量代码改善的157个建议【20-22】[泛型集合、选择集合、集合的安全]

    建议20.使用泛型集合来替代非泛型集合 http://www.cnblogs.com/aehyok/p/3384637.html 这里有一篇文章,是我之前专门来介绍泛型的.我们应尽量的使用泛型集合.因 ...

  2. C#程序编写高质量代码改善的157个建议【16-19】[动态数组、循环遍历、对象集合初始化]

    前言   软件开发过程中,不可避免会用到集合,C#中的集合表现为数组和若干集合类.不管是数组还是集合类,它们都有各自的优缺点.如何使用好集合是我们在开发过程中必须掌握的技巧.不要小看这些技巧,一旦在开 ...

  3. C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

    前言 本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html .本文主要学习记录以下内容: 建议13.为类型输出格式化字符串 建议14.正确实现浅拷贝和深 ...

  4. C#程序编写高质量代码改善的157个建议【10-12】[创建对象时需要考虑是否实现比较器、区别对待==和Equals]

    前言 建议10.创建对象时需要考虑是否实现比较器 建议11.区别对待==和Equals 建议12.重写Equals时也要重写GetHashCode 建议10.创建对象时需要考虑是否实现比较器 有对象的 ...

  5. C#程序编写高质量代码改善的157个建议【4-9】[TryParse比Parse、使用int?来确保值类型也可以为null、readonly和const、0值设为枚举的默认值、避免给枚举类型的元素提供显式的值、习惯重载运算符]

    建议4.TryParse比Parse好 如果注意观察,除string之外的所有的基元类型.会发现它们都有两个将字符串转换为自身类型的方法:Parse和TryParse.以类型double为例. 两者最 ...

  6. 博友的 编写高质量代码 改善java程序的151个建议

    编写高质量代码 改善java程序的151个建议 http://www.cnblogs.com/selene/category/876189.html

  7. 编写高质量代码--改善python程序的建议(六)

    原文发表在我的博客主页,转载请注明出处! 建议二十八:区别对待可变对象和不可变对象 python中一切皆对象,每一个对象都有一个唯一的标识符(id()).类型(type())以及值,对象根据其值能否修 ...

  8. 编写高质量代码--改善python程序的建议(八)

    原文发表在我的博客主页,转载请注明出处! 建议四十一:一般情况下使用ElementTree解析XML python中解析XML文件最广为人知的两个模块是xml.dom.minidom和xml.sax, ...

  9. 编写高质量代码改善java程序的151个建议——导航开篇

    2014-05-16 09:08 by Jeff Li 前言 系列文章:[传送门] 下个星期度过这几天的奋战,会抓紧java的进阶学习.听过一句话,大哥说过,你一个月前的代码去看下,慘不忍睹是吧.确实 ...

随机推荐

  1. unittest加载测试用例名称必须以test开头,是否可以定制化

    ​ 前几天,在一个群里,一个人问了,这样一个问题.说他面试遇到一个面试官,问他,为啥unittest的测试用例要用test 开头,能不能定制化.他不知道为啥. 看到这个题目,我回答当然可以了,可以用l ...

  2. python——高阶函数:高阶函数

    python高阶函数 00初识高阶函数 一等公民 函数在python中是一等公民(First-Class Object),同样和变量一样,函数也是对象,只不过是可调用的对象,所以函数也可以作为一个普通 ...

  3. 使用vue-cli搭建项目开发环境

    一.前言 本篇文章主要是使用vue-cli搭建一个简单的vue项目,这个项目在其他文章中作为代码演示的环境会一直使用. 注意:默认大家的电脑已经安装nodejs,所以这里不总结nodejs的安装. 二 ...

  4. Mysql中,update语句引起的时间戳自动更新问题

    前几天遇到一个奇怪的问题. 在Mysql数据库中有一张表,表中有一个字段是timestamp类型的.我在update别的字段时,这个timestamp字段的时间会自动更新为当前时间. 后来发现,是My ...

  5. [spark程序]统计人口平均年龄(本地文件)(详细过程)

    一.题目描述 (1)编写Spark应用程序,该程序可以在本地文件系统中生成一个数据文件peopleage.txt,数据文件包含若干行(比如1000行,或者100万行等等)记录,每行记录只包含两列数据, ...

  6. poj 3281 Dining (Dinic)

    Dining Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 22572   Accepted: 10015 Descript ...

  7. Qt Framework 问题之 framework/Versions/A:bundle format unrecognized, invalid, or unsuitable

    在解决标题提到的问题之后,先来介绍下Qt Framework一些基本知识. 基于QT的Mac端工程,在打包时需要对所有需要嵌入到APP的framework及dylib文件进行手动签名处理. 一.签名处 ...

  8. [ch02-02] 非线性反向传播

    系列博客,原文在笔者所维护的github上:https://aka.ms/beginnerAI, 点击star加星不要吝啬,星越多笔者越努力. 2.2 非线性反向传播 2.2.1 提出问题 在上面的线 ...

  9. vue跨域处理

    本人对于vue跨域处理流程不是很清楚,特此理顺一遍. 1.在config中进行配置,该文件不是都存在,需要自己建: proxyTable,这个参数主要是一个地址映射表,你可以通过设置将复杂的url简化 ...

  10. linux运维与实践

    1.容器云计算节点负载值高,通过top可以看到Load Average:70.1  71.3  70.8,虚拟机有8个cpu: cpu使用率高导致(R状态)? 同时在top中观察一段时间,消耗cpu最 ...