提高你的C#程序编码质量
摘自陆敏技之《编写高质量代码:改善C#程序的157个建议》,编写C#程序代码时应考虑代码效率、安全和美观,可参考下述建议。想成为一名合格的搬砖工,牢记吧!!
1、正确操作字符串
1) 避免装箱操作。如语句:String str = "hans"+8 就存在装箱操作,建议改成语句:String str = "hans"+8.ToString()
2) 使用StringBuilder代替String运算(经测试,当执行5000次加运算时,StringBuilder效率是String的近倍)。C#中String一旦被赋值不可改变,进行任何操作(+,=)都会在内存中创建一个新的字符串对象,会给运行计算带来额外开销。而StringBuilder并不会重新创建一个新的String对象,StringBudiler每次执行+操作时,如果内容空间不够(默认长度16),会重新加倍进行分配空间。
//耗时3000毫秒
String str = "";
for (int i = ; i < ; i++) {
str += i.ToString();
} //耗时5毫秒
StringBuilder sb = new StringBuilder();
for (int i = ; i < ; i++)
{
sb.Append(i.ToString());
}
3) 常使用String.Format方法。 String.Format在内部使用了StringBuilder方法格式化字符串,效率很高,且代码美观,可读性高。
2、类型转换
1) 常使用as转换类型。as转换类型效率高,类型转换失败且不会报异常,而是值为null。
2) 使用TryParse代替Parse。Parse转型失败会引发异常,异常过程会消耗性能,而TryParse转型失败无异常,out操作符将参数设置为0。经测试,当转型失败时,TryParse效率要比Parse高几百倍。
3) 使用int?使得值类型也可以为nul。T?是Nullable<T>的简写,值可以为nul。T?判断是否为nul可用简写操作符??,如int? i=22;int j = i ?? 0。
3、区别readonly和const使用方法。const是一个编译期常量,readonly是一个运行时常量。const在编译时,会将常量用对应的值替代,运行效率高,而readonly在运行时初始化,初始化后不可修改,运行效率比const低,但是灵活性高。readonly赋值发生在运行时,赋值后不可改变表示:1) 值类型,只本身不可改变 2) 引用类型,引用指针不可改变,即不可修改指针指向新对象,但对象内容可修改。
4、避免给enum枚举类型的元素提供显示的值。在如下枚举类型Week中增加一个元素,输出ValueTemp的值等于Wednesday,原因是ValueTemp定义时没有赋值,编译期会逐个为元素值+1,当编译器发现ValueTemp时,会在Tuesday = 2的基础上+1,所以ValueTemp实际赋值为3,与Wednesday=3相等。
enum Week { Monday = 1, Tuesday = 2, ValueTemp, Wednesday = 3, Thursday = 4, Friday = 5, Saturday = 6, Sunday = 7 }
5、熟悉运算符重载。运算符重载可使得对象运算操作简洁方便,参考下例:
class Salary
{
public int RMB { get; set; } //运算符重载
public static Salary operator +(Salary s1, Salary s2) {
s2.RMB += s1.RMB;
return s2;
}
} //调用
Salary s1 = new Salary() { RMB = };
Salary s2 = new Salary() { RMB = };
Salary s3 = s1 + s2; //运用运算符重载
6、实现深拷贝和浅拷贝。浅拷贝是将对象中的所有字段复制到新的对象中,其中,值类型字段拷贝后副本的修改不会影响源对象对应值,而引用字段拷贝后与原字段指向同一对象地址,副本值修改后会影响源对象对应的值(参考《C#值类型与引用类型区别》);深拷贝是将对象中的所有值类型和引用类型字段复制到新对象中,引用类型字段重新创建引用对象,副本的修改不影响源对象对应值。
//浅拷贝 继承ICloneable接口并实现Clone方法
class Salary : ICloneable
{
public int RMB { get; set; } public object Clone()
{
//实现浅拷贝
return this.MemberwiseClone();
}
} //深拷贝,通过对象序列化和反序列化实现,继承接口ICloneable并实现方法Clone
[Serializable]
class Salary : ICloneable
{
public int RMB { get; set; } 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 Salary;
}
}
}
7、使用dynamic简化反射操作。 dynamic是Framework 4.0的新特性,可以使C#具有弱语言的特性。
1) var与dynamic的区别:var在编译的时候替换成自动匹配的实际类型,而dynamic被编译后实际上是一个Object类型,只是编译期会进行特殊处理,在编译器不进行任何的类型检查,而是将类型检查放到了运行期。
2) 反射的优化,参考下例:
class A
{
public String Name { get; set; } public int Add(int a, int b)
{
return a + b;
}
} //调用
A a1 = new A();
MethodInfo m = a1.GetType().GetMethod("Add"); //普通反射 耗时1084ms
for (int i = ; i < ; i++)
{
int re = (int)m.Invoke(a1, new object[] { , });
} //优化后的反射 耗时13ms
var delg = (Func<A, int, int, int>)Delegate.CreateDelegate(typeof(Func<A, int, int, int>), m);
for (int i = ; i < ; i++)
{
delg(a1, , );
} //使用dynamic优化反射 耗时60ms
dynamic a2 = new A();
for (int i = ; i < ; i++) {
a2.Add(, );
} //使用dynamic优化反射 耗时60ms
dynamic a2 = new A();
for (int i = ; i < ; i++) {
a2.Add(, );
}
8、使用Environment.NewLine获取当前环境下的换行符号。
9、使用params减少重复参数。如方法:public void pap(String a,String b,String c){ } 可简写为:public void pap(params String[] args){ }。注意:① params数组必须是方法的最后一个参数 ② 不允许out或ref数组
10、扩展类型中的方法。扩展方法是一种特殊的静态方法,可以为类型扩展方法而无需创建新的派生类型。本实例扩展了String类型添加了扩展方法Test():
//自定义扩展类 必须为静态类
static class StringExtenstion
{
//扩展String类添加方法Test,必须为静态方法,参数格式为:this 类型名称 对象
//调用方法如:String str="hans"; Console.WriteLine(str.Test()); --输出my string
public static String Test(this String str) {
return "my string";
}
}
1、对象和集合初始化:Person person = new Person(){ Name="hans",Age = 25 };
2、匿名类型:var persion = new { Name="hans",Age=25 }; 编译器会自动生成具有对应字段的匿名类。
3、LINQ查询中避免不必要的迭代。充分运用First和Take等方法,查询到符合条件的记录就立即返回,而不是所有结果返回再筛选,效率可大幅度提高。
1、继承IDispose接口的类型,实例化可用using语法。using会在结束时,自动调用对象的Dispose方法。
2、通用BinarySerializer序列化。BinarySerializer.cs:
class BinarySerializer
{ //将类型序列化为字符串
public static string Serialize<T>(T t)
{
using (MemoryStream stream = new MemoryStream()) {
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, t);
return System.Text.Encoding.UTF8.GetString(stream.ToArray());
}
} //将类型序列化为文件
public static void SerializeToFile<T>(T t, string path, string fullName)
{
if (!Directory.Exists(path)) {
Directory.CreateDirectory(path);
}
string fullPath = string.Format(@"{0}\{1}", path, fullName);
using (FileStream stream = new FileStream(fullPath, FileMode.OpenOrCreate)) {
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, t);
stream.Flush();
}
} //将字符串反序列化为类型
public static TResult Deserialize<TResult>(string s) where TResult : class
{
byte[] bs = System.Text.Encoding.UTF8.GetBytes(s);
using (MemoryStream stream = new MemoryStream(bs)) {
BinaryFormatter formatter = new BinaryFormatter();
return formatter.Deserialize(stream) as TResult;
}
} //将文件反序列化为类型
public static TResult DeserializeFromFile<TResult>(string path) where TResult : class
{
using (FileStream stream = new FileStream(path, FileMode.Open)) {
BinaryFormatter formatter = new BinaryFormatter();
return formatter.Deserialize(stream) as TResult;
}
}
}
3、序列化特性说明:
1) Serializable:用于类,指示一个类可以序列化;
2) NonSerialized:用于字段,指示一个字段不被序列化。因为属性的本质是方法,因此NonSerialized不可直接用于属性,可用于自己实现的属性;
3) OnDeserialized:应用于某方法时,会指定在对象反序列化后立即调用此方法;
4) OnDeserializing:应用于某方法时,会指定在对象反序列化时调用此方法;
5) OnSerialized:如果将对象图应用于某方法,则应指定在序列化该对象图后是否调用该方法;
6) OnSerializing:当他应用于某个方法时,会指定在对象序列化前调用此方法;
1、异步与多线程。异步与多线程两者度可以达到避免调用线程阻塞的目的,从而提高软件的可响应性。很多时候,我们分不清异步与多线程的区别,经常经他们混为一谈,其实,他们还是有区别的:
1) 异步操作本质:所有的程序最终都会由计算机硬件来执行,拥有DMA功能的硬件在和内存进行数据交互的时候可以不消耗CPU资源,这些不消耗CPU时间的I/O操作正是异步操作的硬件基础。所以即使在DOS这样的单进程系统中也同样可以发起异步的DMA操作。优点:异步操作无需额外线程负担,并且使用了回调的方式进行处理,在设计良好的情况下,处理函数可尽可能减少共享变量的使用,减少了死锁发生的可能性。缺点:异步操作编写复杂,回调难以调试。
2) 线程本质:线程不是一个计算机硬件的功能,而是操作系统提供的一种逻辑功能。线程本质上是进程总一段并发执行的代码,所以线程需要操作系统投入CPU资源来运行和调度。优点:编写简单。缺点:线程使用会消耗额外切换带来的负担,并且线程间共享变量可能造成死锁。
3) 适用范围:多线程适用于计算密集型工作,异步机制适用于IO密集型工作,详细参考图1。
图1 单线程、多线程适用条件
一个使用了异步操作的WinForm程序示例如下所示,点击按钮,异步获取网页源码并显示在窗体的文本控件textBox1上。
private void button1_Click(object sender, EventArgs e)
{
//开辟一个线程
Thread t = new Thread(()=>{
var request = HttpWebRequest.Create("http://www.cnblogs.com/hanganglin");
//发起异步请求
request.BeginGetResponse(this.AsyncCallbackImpl, request);
});
t.Start();
} //回调方法
public void AsyncCallbackImpl(IAsyncResult ar) {
WebRequest request = ar.AsyncState as WebRequest;
var response = request.EndGetResponse(ar);
var stream = response.GetResponseStream();
using (StreamReader reader = new StreamReader(stream)) {
var content = reader.ReadToEnd();
//由于textBox1控件是主线程创建的,在其他线程中需要调用必须采用异步机制
//如果InvokeRequired为True,则必须通过异步来修改,否则可直接修改
if (textBox1.InvokeRequired) {
textBox1.BeginInvoke(new Action(() => {
textBox1.Text = content;
}));
}
else {
textBox1.Text = content;
}
}
}
值得注意的是,创建控件线程以外的线程想访问控件,可通过控件的BeginInvoke异步方法,BeginInvoke方法是将消息发送到消息队列中等待UI所在的线程进行处理,代码:if(textBox1.InvokeRequired){ textBox.BeginInvoke(new Action(()=>{ textBox.Text = content; })); } else { textBox1.Text = content; }
1、声明变量时考虑最大值,关键字check可检查运算是否溢出,运算溢出则抛出异常。 代码:check{ ... }。
2、文件MD5哈希值判断文件内容是否修改。对文件求MD5哈希值,当文件内容被修改后再求MD5哈希值,比较两个值可判断文件内容是否被修改过。
//获取文件的md5哈希值
public static String GetFileMd5Hash(String filePath) {
using(MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider())
using(FileStream fs = new FileStream(filePath,FileMode.Open,FileAccess.Read,FileShare.Read)){
return BitConverter.ToString(md5.ComputeHash(fs)).Replace("-", "");
}
}
3、合适选择使用对称加密和非对称加密。对称加密加密和解密时使用了相同的密钥和加密算法,其优点是加密解密速度快,常用于大量数据传输,缺点是传输数据时需要传输密钥, 安全系数不高。非对称加密使用了不同的密钥,公钥PK和私钥SK,用公钥PK进行加密,只有用对应的私钥SK才可以解密,优点是传输加密信息时不需要传输私钥,安全系数高,缺点是算法复杂,加密解密速度很慢。
C#下的一个文件对称加密示例MySymmetricAlgorithm:
public class MySymmetricAlgorithm
{
//缓冲区大小
static int bufferSize = * ;
//密钥salt 防止“字典攻击”
static byte[] salt = { , , , , , , , , , , , , , , , };
//初始化向量
static byte[] iv = { , , , , , , , , , , , , , , , }; //初始化并返回对称加密算法
static SymmetricAlgorithm CreateRijndael(string password, byte[] salt)
{
PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, salt, "SHA256", );
SymmetricAlgorithm sma = Rijndael.Create();
sma.KeySize = ;
sma.Key = pdb.GetBytes();
sma.Padding = PaddingMode.PKCS7;
return sma;
} public static void EncryptFile(string inFile, string outFile, string password)
{
using (FileStream inFileStream = File.OpenRead(inFile), outFileStream = File.Open(outFile, FileMode.OpenOrCreate))
using (SymmetricAlgorithm algorithm = CreateRijndael(password, salt)) {
algorithm.IV = iv;
using (CryptoStream cryptoStream = new CryptoStream(outFileStream, algorithm.CreateEncryptor(), CryptoStreamMode.Write)) {
byte[] bytes = new byte[bufferSize];
int readSize = -;
while ((readSize = inFileStream.Read(bytes, , bytes.Length)) != ) {
cryptoStream.Write(bytes, , readSize);
}
cryptoStream.Flush();
}
}
} public static void DecryptFile(string inFile, string outFile, string password)
{
using (FileStream inFileStream = File.OpenRead(inFile), outFileStream = File.OpenWrite(outFile))
using (SymmetricAlgorithm algorithm = CreateRijndael(password, salt)) {
algorithm.IV = iv;
using (CryptoStream cryptoStream = new CryptoStream(inFileStream, algorithm.CreateDecryptor(), CryptoStreamMode.Read)) {
byte[] bytes = new byte[bufferSize];
int readSize = -;
int numReads = (int)(inFileStream.Length / bufferSize);
int slack = (int)(inFileStream.Length % bufferSize);
for (int i = ; i < numReads; ++i) {
readSize = cryptoStream.Read(bytes, , bytes.Length);
outFileStream.Write(bytes, , readSize);
}
if (slack > ) {
readSize = cryptoStream.Read(bytes, , (int)slack);
outFileStream.Write(bytes, , readSize);
}
outFileStream.Flush();
}
}
}
1、区分接口和抽象类的应用场合。接口与抽象类的区别:① 接口支持多继承,抽象类只能但继承; ② 接口可以包含方法、属性、索引器、事件的签名,但不能有实现,抽象类则可以通过虚方法来实现; ③ 接口新增方法后,所有继承者必须重构,否则编译不通过,而抽象类新增虚方法后不需要(新增抽象方法也需重构)。由于存在这些区别,接口一旦被设计出来,就应该是不变的,而抽象类可以随着版本的升级增加一些功能。接口与抽象类的应用场景简单可概括为:① 如果对象存在若干功能相近且关系紧密的版本,则使用抽象类; ② 如果对象关系不紧密,但是若干功能拥有共同的声明,则使用接口; ③ 抽象类适合于提供丰富功能的场合,接口则更倾向于提供单一的一组功能。
2、优先考虑组合(Has a),然后考虑继承(Is a)。组合是将其他类型的对象作为本类型的成员使用,而继承是子类继承父类并使用。组合好比"黑盒式代码使用",继承好比"白盒式代码使用"。组合的耦合性比继承更低,封装性比继承更高。
3、开闭原则。开闭原则是面向对象设计中最重要的原则之一,是可复用设计的基石。开闭原则原话翻译:软件实体应该对扩展开放,对修改关闭。通俗地说,在软件体系扩展新功能时,不应该修改现有的代码。
1、命名术语:
1) PascalCasing,帕斯卡命名法(首字母大写),公开元素建议使用帕斯卡命名法。建议用于命名空间、类型、接口、方法、属性、事件、静态字段和枚举值。
2) camelCasing,驼峰命名法(首字母小写),非公开元素建议使用驼峰命名法。建议用于参数、私有字段和方法内变量。
2、命名规范:
1) 命名空间:使用Java中的域名域名命名法,或使用公司名作为前缀,产品名称作为第二层,其他特性作为第三层,如:PanChina.Oa.System。
2) 类型:使用名词或名词词组进行命名(如UserManager要优于UserManage),不要在类型名前加前缀,派生类名称以基类名称结尾(如Exception所有派生类都以Exception结尾)。
3) 接口:使用大写字母"I"为前缀,用形容词命名,如IDisposable表示类型可以被释放。
4) 泛型:使用大写字母"T"为前缀,多个参数使用标号,如T1、T2。
5) 枚举:枚举类型用复数命名,不要添加如"Enum"或"Flag"等后缀,枚举元素用单数命名。如enum Week { Monday,Tuesdat,.. }
6) 字段:共有字段使用帕斯卡命名法,私有字段使用驼峰命名法。使用名词或名词词组命名,不添加前缀。
7) 方法:使用动词词组命名,根据方法对应的任务命名,而不是根据内部实现细节来命名。常用动词:Get、Update、Delete、Add、Validate、Select、Search等,动词后加上动作内容,就是一个规范的方法名。
8) 属性:用名词或名词词组命名,要用肯定性的短语,如CanSeek,而不是否定短语CantSeek。当属性对应一个类型时,建议则接用类型命名属性名,如:public Company Company{ get;set; },不建议为属性制定另外名字,如TheCompany。
9) 事件:用动词或动词词组命名(如Cheked、Updated、Selected等词组),以"EventArgs"后缀结尾(绑定事件的方法名加上On)。如:UpdatedEventArgs,绑定事件方法OnUpdated()。
10) 索引器:固定设计,使用this关键字,如:public String this[int index] { get {return "";} }。
3、有条件地使用前缀,在.NET设计规范中,不建议使用前缀,如果确有特殊使用需求,建议:① 前缀m_,表示这是一个实例变量 ② 前缀s_,表示这是一个静态变量。
提高你的C#程序编码质量的更多相关文章
- [转]JavaScript程序编码规范
原文:http://javascript.crockford.com/code.html 作者:Douglas Crockford 译文:http://www.yeeyan.com/articles/ ...
- Microsoft.VisualBasic.dll的妙用and 改善C#公共程序类库质量的10种方法
Microsoft.VisualBasic.dll的妙用(开发中肯定会用到哦) 前言 做过VB开发的都知道,有一些VB里面的好的函数在.NET里面都没有,而Microsoft.VisualBasic. ...
- 提高ASP.NET应用程序性能的十大方法
一.返回多个数据集 检查你的访问数据库的代码,看是否存在着要返回多次的请求.每次往返降低了你的应用程序的每秒能够响应请求的次数.通过在单个数据库请求中返回多个结果集,可以减少与数据库通信的时间,使你的 ...
- 提高Asp.Net应用程序性能的十大方法(译感)
译完了提高Asp.Net应用程序的十大方法这篇文章,仔细想其中提到的每一条,在这里结合我的项目来谈谈.第一条:返回多个结果集因为我的项目中所有对数据库的访问的sql语句都是通过调用存储过程实现的,所以 ...
- 程序编码(机器级代码+汇编代码+C代码+反汇编)
[-1]相关声明 本文总结于csapp: 了解详情,或有兴趣,建议看原版书籍: [0]程序编码 GCC调用了一系列程序,将源代码转化成可执行代码的流程如下: (1)C预处理器扩展源代码,插入所有用#i ...
- JavaEE程序编码规范
JavaEE程序编码规范 目 录 JAVA程序编码规范1 1变量的命名规则1 1.1常量(包含静态的)1 1.2类变量(静态变量)及实例变量1 1.3局部变量1 1.4参数2 1.5其它2 2方法 ...
- qt creator修改程序编码(解决中文乱码问题)的方法
qt creator修改程序编码(解决中文乱码问题)的方法 qt creator修改程序编码的功能有几处. 1.edit - select encoding 选择载入(显示)编码和储存编码,其中GB2 ...
- PHP团队编码质量提升之道
这段文字其实只是标题党. 目前PHP猿的薪资水平普遍较高,但其实绝大多数PHP猿都不是科班出身,你问一个什么是OOP的问题可能都说不清楚. 在团队中,除了费力的去普及编程语言的基础知识,要想提高开发质 ...
- Golang 编写的图片压缩程序,质量、尺寸压缩,批量、单张压缩
目录: 前序 效果图 简介 全部代码 前序: 接触 golang 不久,一直是边学边做,边总结,深深感到这门语言的魅力,等下要跟大家分享是最近项目 服务端 用到的图片压缩程序,我单独分离了出来,做成了 ...
随机推荐
- Redis 笔记与总结5 Redis 常用命令之 键值命令 和 服务器命令 && 高级应用之 安全性 和 主从复制
Redis 提供了丰富的命令对数据库和各种数据库类型进行操作,这些命令可以在 Linux 终端使用. 1. 键值相关命令: 2. 服务器相关命令 键值相关命令 ① keys 命令 返回满足给定 pat ...
- redis列表list
Redis Rpush 命令 Redis 列表(List) Redis Rpush 命令用于将一个或多个值插入到列表的尾部(最右边). 如果列表不存在,一个空列表会被创建并执行 RPUSH 操作. ...
- WITCH CHAPTER 0 [cry] 绝密开发中的史克威尔艾尼克斯的DX12技术演示全貌
西川善司的[WITCH CHAPTER 0 cry]讲座 ~绝密开发中的史克威尔艾尼克斯的DX12技术演示全貌 注:日文原文地址: http://pc.watch.impress.co.jp/d ...
- Android 通过网页打开自己的APP(scheme)
Android 通过网页打开自己的APP(scheme) 分类: android2014-07-09 17:35 8565人阅读 评论(2) 收藏 举报 通过用手机的浏览器(内置,第三方都可)访问一个 ...
- XBox 开发者大会
今天参加了微软的Xbox开发者大会,虽然没我什么事情,不过还是有不少的收获,随便说说自己的一点感受吧. 先上几张图,附带妹子一个,不过手机不清楚哈,~~ 1 ID@XBOX开发者计划与独立游戏开发者 ...
- 【转载】C编译过程概述
gcc:http://www.cnblogs.com/ggjucheng/archive/2011/12/14/2287738.html#_Toc311642844 gdb:http://www.cn ...
- Android TextView 显示不全的自动补齐方式
TextView在Android开发中用到的地方应该是很多的.很多时候,TextView会有一行显示不全被截取或者会换行.之前我的解决办法比较笨拙,定死TextView的一行字数长度,最后一个以省略号 ...
- 弹窗文件js+css
// 每个弹窗的标识 var x =0; var idzt = new Array(); var Window = function(config){ //ID不重复 idzt[x] = " ...
- freebsd 显示中文
来自 :http://francs3.blog.163.com/blog/static/405767272014659311700/ 只需在 ~/.cshrc 文件添加以下几行即可. --3 在~/. ...
- 【Java 基础篇】【第七课】组合
我所理解的组合就是在一个类当中又包含了另一个类的对象. 这样的方式就是组合吧: 电池是一个类,有电量 手电筒需要电池 看代码吧: // 电池类 class Battery { // 充电 public ...