zoukankan      html  css  js  c++  java
  • C#几个经常犯错误汇总

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

    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接口手动释放内存

    在.NET Framework中,对象的处理和使用一样重要,理想的方法是在使用完对象的时候,在类中实现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方法释放内存是否会推迟释放等观点都是分应用场景而言,算作抛砖引玉吧,非绝对。最后谢园友们指点。

  • 相关阅读:
    简单的模板解析函数
    HTML通过事件传递参数到js 二 event
    HTML通过事件传递参数到js一
    通过this获取当前点击选项相关数据
    LeetCode 20. 有效的括号(Valid Parentheses)
    LeetCode 459. 重复的子字符串(Repeated Substring Pattern)
    LeetCode 14. 最长公共前缀(Longest Common Prefix)
    LeetCode 168. Excel表列名称(Excel Sheet Column Title)
    LeetCode 171. Excel表列序号(Excel Sheet Column Number) 22
    LeetCode 665. 非递减数列(Non-decreasing Array)
  • 原文地址:https://www.cnblogs.com/zhijianliutang/p/2407688.html
Copyright © 2011-2022 走看看