博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C#几个经常犯错误汇总
阅读量:6655 次
发布时间:2019-06-25

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

原文地址:http://www.cnblogs.com/zhijianliutang/archive/2012/03/20/2407688.html

在我们平常编程中,时间久了有时候会形成一种习惯性的思维方式,形成固有的编程风格,但是有些地方是需要斟酌的,即使是一个很小的错误也可能会导致昂贵的代价,要学会善于总结,从错误中汲取教训,尽量不再犯同样错误,注重编程之美,代码的优雅,总结几个平常经常犯的错误。

1、在C#编程中,字符型类型是最容易处理出错的地方,代价是非常昂贵,在.Net Framwork中,字符串是一个相当特别的引用类型,string本省就是一个不可继承的密封类,但是它具有了值类型所应用的特点,但是它在CLR中内存还是保存于托管堆之上,也就是说,当我们每次定义一个字符串类型的时候,就在堆内存中开辟一端内存,而当我们字符串被修改之后,它会创建一个新的内存,注意这里的内存是不连续的,而是通过修改栈内地址引用而拼凑字符串,不会改变源字符串在内存中的地址,所以有些程序员总是喜欢使用这样的方法格式化字符串:

string  SelectText="select * from "+TableName+" where UserName='"+Name+"'";
上述代码,使用了字符串拼凑的方法,因为使用了多重串联,因此会在内存中创建两个不必要的字符串垃圾副本。
其实在C#中,已经为我们提供了StringBuilder和String.Fromat来解决此问题,虽然他们可以实现同样的功能,但是他们有质的变化,StringBuilder在内存中开辟的是一段连续内存,当增加新字符串时候,它会在栈中指向的同一个堆内存中连续存放字符,这就形成了性能的提升。所以我们将上面代码改成:

string SelectText=string.Format("select  *  from {0} where UserName={1}",TableName,Name);
2、大多数开发人员都不知道内置的验证数据类型的方法,如System.Int32,因此很多人都是自己实现的,其实这是不妥的,因为这些基本类型中都存在自己固有的类型验证方法,下面这个就是自己实现验证的一个字符串是否是数值的代码:

public bool CheckIfNumeric(string value){    bool IsNumeric=true;    try     {           int i=Convert.ToInt32(value);     }     catch(FormatException excepiton)     {           IsNumeric=false;     }     return IsNumeric;}
虽然使用了try catch语句,这不是最佳的做法,更好的方法是下面使用Int.TryParse;

int output=0;bool IsNumeric=int.TryParse(value,out output);
int.TryParse是更快、更简洁的方法。
3、自己利用IDisposable接口手动释放内存
在.NETFramework中,对象的处理和使用一样重要,理想的方法是在使用完对象的时候,在类中实现IDisposable接口中的dispose方法进行内存的释放,当然在.Net本身提供的垃圾回收机制(GC)中就提供了这样的功能,在我们实例化类对象时,在类本身的析构函数中会调用dispose方法,GC在各级内存堆满的情况下,自动检查对象使用情况,去相应的释放内存,但是运行在非托管平台上的方法,需要我们自己手动释放内存,比如我们常见的SqlConnection对象,也就有了下面的创建、使用和处理方法:

public void  DALOneMethod(){    SqlConnection  connection=null;    try     {          connection =new SqlConnection("。。。。。。。。。。。");          connection.Open();          //sqlcommand。。run     }     catch(Exception exception)     {             // manager exception     }     finally     {            connection.Close();            connection.Disopse();     }}
上述代码是大部分程序员会出现的代码,乍看没啥问题,连接处理在最后一个代码中被明确调用,但是如果发生了一个异常,catch代码块就被执行,然后再执行最后一个代码块处理连接,因此在最后一个代码块执行之前,连接将一直留在内存中,大部分我们会在此处记录错误,一般涉及到IO操作,如果延时时间比较长的话,这个连接将在内存时间长时间停留。我们一个原则就是当对象不再使用的时候我们里面释放资源。
我们采用程序逻辑域来处理这个问题会更好:

public void  DALOneMethod(){    using(SqlConnction  connection=new SqlConnection("。。。。。。。"))     {         connction.Open();      // do SUAD     }}
当使用using代码快时,对象上的dispose()方法将在执行推出逻辑域的时候调用,这样就保证了SqlConnection的资源处理被尽早释放,当然这个方法也适用于实现IDisposable接口的类,当时个人不推荐这样做,在非常有把握的情况下可以手动释放,但是没把握还是叫给.net系统释放,因为本身类的析构函数就实现这个方法,当我们自己重写后,反而会导致系统误以为你自己定义了方法,而推迟释放资源,有兴趣可以研究下GC运行本质,假如能在第一代被释放的内存,如果我们重写dispose方法反而推迟到第二代内存堆中释放,显然是不可取的。
4、学会合理的管理公共变量,我们在系统中经常会滥用公共变量,没有做到合适的封装好。

static  void Main(string[]  args){            MyAccount  account=new MyAccount();           //这地方不能随便的调用account里面的字段进行更改,但是缺改了        account.AccountNumber="ddddddddd";           Console.ReadKey();}public class MyAccount{          public  string AccountNumber;          public  MyAcctount()          {                AccountNumber="ssssssssssssss";          }}
在上面的MyAccount类中生命了一个AccountNumber公共变量,理想情况下,AccountNumber应该是只读的,不能让外界修改,但是这里MyAccount类却没有对它做任何控制。
声明公共做法应该是使用属性,如:

public  class  MyAccount{   private stirng _accountNumber;   public  string AccountNumber   {        get {  return  _accountNumber;  }   }   public  MyAccount()   {          _accountNumber="dddddddd";   }}
这里我们封装了AccountNumber公共变量,它变成了只读,不能由调用者类进行修改。
5、嵌套的异常处理,有的开发人员喜欢在方法末尾加上处理的嵌套方法,如

public class NestedExceptionHandling  {      public void MainMethod()      {          try          {              //some implementation              ChildMethod1();          }          catch (Exception exception)          {              //Handle exception          }      }        private void ChildMethod1()      {          try          {              //some implementation              ChildMethod2();          }          catch (Exception exception)          {              //Handle exception           throw;            }      }        private void ChildMethod2()      {          try          {              //some implementation          }          catch (Exception exception)          {              //Handle exception              throw;          }      }  }
如果相同的异常被处理多次,性能开销将会增加。
我们的解决方法是让异常处理方法独立开来,如:

public class NestedExceptionHandling  {      public void MainMethod()      {          try          {              //some implementation              ChildMethod1();          }          catch(Exception exception)          {              //Handle exception          }      }        private void ChildMethod1()      {          //some implementation          ChildMethod2();      }        private void ChildMethod2()      {          //some implementation      }  }
6、大数据量上使用Dataset和DataReader混用,当单表数据量很大的情况,使用DataSet是一种很不明智的选择,应为DataSet是以DataTable内存形式存放数据量,一次性将数据拖入内存,当数据很大的情况下,这种方式是很吃内存的,相比DataSer,DataReader就显得优雅很多,它是每次读取一条数据,然后轮询调用机制,但是也有它的弊端,就是相对长连接,但是对内存消耗而言这是有利的,当然DataSet在大部分应用场景下也是有自己的优点,充分解耦、一次性操作、领域模型操作等方面,两者分情况分场景而用,这里只是稍微提提,根据场景分析区别。
 内容更正
原篇文章不动,感谢园友的点评,更正几处内容
1、第一条String类型内存消耗问题,举的例子不到位,在字符串数量少的时候性能没有影响的,但就在.net Framwork平台运行,分析应该就是此原理了。
现将老赵分析的结论归结如下:
     <1>对于字符串数量比较少的情况(从数据上来看大约是5-6个),StringBuilder的性能并不比普通连接操作来的快。因此,在任何地方都使用StringBuilder是不恰当的做法。    
参照:http://blog.zhaojie.me/2009/11/string-concat-perf-1-benchmark.html
另附性能比较源码同样出自老赵博文,有兴趣的园友可自行比较测试:http://www.cnblogs.com/zhijianliutang/archive/2011/12/17/2291323.html
2、类对象在使用完对象后并不是通过析构函数调用Dispose方法实现垃圾回收,Dispose是.net类库提供的一个释放内存的方法,供开发人员自行调用,它是通过Finalizer是供GC调用。
关于其他SQL注入、属性公开知否妥当、自行调用Dispose方法释放内存是否会推迟释放等观点都是分应用场景而言,算作抛砖引玉吧,非绝对。最后谢园友们指点。

你可能感兴趣的文章
异常类的设计和使用技巧
查看>>
java编码
查看>>
DB2 9.7 备份还原
查看>>
Android Browser学习十四 NetworkStateHandler网络状态监听和处理
查看>>
Linux IO实时监控iostat命令详解
查看>>
DateFormat 线程不安全
查看>>
使用二进制位进行权限控制
查看>>
【spring】spring 通过ApplicationContextAware 获取bean
查看>>
想知道&&与&及||与|之间的区别吗?
查看>>
Base62x算法改进并增加Base62x in Python
查看>>
小项目创意大集合
查看>>
php汉字繁简体互转扩展:openccpp
查看>>
Bootstrap 进度条
查看>>
Mysql 学习记录(一)
查看>>
some Linux command
查看>>
js表情图实现
查看>>
MySql中的排序规则utf8_unicode_ci、utf8_general_ci的区别总结
查看>>
sqoop常见错误
查看>>
Android与JS之JsBridge使用与源码分析
查看>>
js中eval详解
查看>>