C#程序员最易犯的编程错误

本文介绍了10种最常见的编程错误,或是C#程序员要避免的陷阱。

常见错误1: 像使用值一样使用参考或过来用
C++以及许多其他语言的程序员习惯于控制他们分配给变量的值是否为简易的值或现有对象的引用。在C#中呢,这将由写该对象的程序员决定,而不是由实例化该对象并对它进行变量赋值的程序员决定。这是新手C#程序员们的共同“问题”。
如果你不知道你正在使用的对象是否是值类型或引用类型,你可能会遇到一些惊喜。例如:

Point point1 = new Point(20, 30);
Point point2 = point1;
point2.X = 50;
Console.WriteLine(point1.X); // 20 (does this surprise you?)
Console.WriteLine(point2.X); // 50

Pen pen1 = new Pen(Color.Black);
Pen pen2 = pen1;
pen2.Color = Color.Blue;
Console.WriteLine(pen1.Color); // Blue (or does this surprise you?)
Console.WriteLine(pen2.Color); // Blue

如你所见,尽管Point和Pen对象的创建方式相同,但是当一个新的X的坐标值被分配到point2时, point1的值保持不变 。而当一个新的color值被分配到pen2,pen1也随之改变。因此,我们可以推断point1和point2每个都包含自己的Point对象的副本,而pen1和pen2引用了同一个Pen对象 。如果没有这个测试,我们怎么能够知道这个原理?
一种办法是去看一下对象是如何定义的(在Visual Studio中,你可以把光标放在对象的名字上,并按下F12键)

public struct Point { … } // defines a “value” type
 public class Pen { … } // defines a “reference” type

  如上所示,在C#中,struct关键字是用来定义一个值类型,而class关键字是用来定义引用类型的。 对于那些有C++编程背景人来说,如果被C++和C#之间某些类似的关键字搞混,可能会对以上这种行为感到很吃惊。

  如果你想要依赖的行为会因值类型和引用类型而异,举例来说,如果你想把一个对象作为参数传给一个方法,并在这个方法中修改这个对象的状态。你一定要确保你在处理正确的类型对象。

常见的错误2:误会未初始化变量的默认值

  在C#中,值得类型不能为空。根据定义,值的类型值,甚至初始化变量的值类型必须有一个值。这就是所谓的该类型的默认值。这通常会导致以下,意想不到的结果时,检查一个变量是否未初始化:

 class Program {
 static Point point1; static Pen pen1; static void Main(string[] args) {
  Console.WriteLine(pen1 == null); // True
  Console.WriteLine(point1 == null); // False (huh?)
 }
 }

  为什么不是【point 1】空?答案是,点是一个值类型,和默认值点(0,0)一样,没有空值。未能认识到这是一个非常简单和常见的错误,在C#中

  很多(但是不是全部)值类型有一个【IsEmpty】属性,你可以看看它等于默认值:

代码如下:

Console.WriteLine(point1.IsEmpty);        // True

  当你检查一个变量是否已经初始化,确保你知道值未初始化是变量的类型,将会在默认情况下,不为空值。

  常见错误3: 使用不恰当或未指定的方法比较字符串

  在C#中有很多方法来比较字符串。

  虽然有不少程序员使用==操作符来比较字符串,但是这种方法实际上是最不推荐使用的。主要原因是由于这种方法没有在代码中显示的指定使用哪种类型去比较字符串。

  相反,在C#中判断字符串是否相等最好使用Equals方法:

代码如下:

public bool Equals(string value);  public bool Equals(string value, StringComparison comparisonType);

  第一个Equals方法(没有comparisonType这参数)和使用==操作符的结果是一样的,但好处是,它显式的指明了比较类型。它会按顺序逐字节的去比较字符串。在很多情况下,这正是你所期望的比较类型,尤其是当比较一些通过编程设置的字符串,像文件名,环境变量,属性等。在这些情况下,只要按顺序逐字节的比较就可以了。使用不带comparisonType参数的Equals方法进行比较的唯一一点不好的地方在于那些读你程序代码的人可能不知道你的比较类型是什么。

  使用带comparisonType的Equals方法去比较字符串,不仅会使你的代码更清晰,还会使你去考虑清楚要用哪种类型去比较字符串。这种方法非常值得你去使用,因为尽管在英语中,按顺序进行的比较和按语言区域进行的比较之间并没有太多的区别,但是在其他的一些语种可能会有很大的不同。如果你忽略了这种可能性,无疑是为你自己在未来的道路上挖了很多“坑”。举例来说:

 string s = "strasse";

 // outputs False:
 Console.WriteLine(s == "straße");
 Console.WriteLine(s.Equals("straße"));
 Console.WriteLine(s.Equals("straße", StringComparison.Ordinal));
 Console.WriteLine(s.Equals("Straße", StringComparison.CurrentCulture));
 Console.WriteLine(s.Equals("straße", StringComparison.OrdinalIgnoreCase));

 // outputs True:
 Console.WriteLine(s.Equals("straße", StringComparison.CurrentCulture));
 Console.WriteLine(s.Equals("Straße", StringComparison.CurrentCultureIgnoreCase));

  最安全的实践是总是为Equals方法提供一个comparisonType的参数。

  下面是一些基本的指导原则:

  当比较用户输入的字符串或者将字符串比较结果展示给用户时,使用本地化的比较(CurrentCulture 或者CurrentCultureIgnoreCase)。

  当用于程序设计的比较字符串时,使用原始的比较(Ordinal 或者 OrdinalIgnoreCase)

  InvariantCulture和InvariantCultureIgnoreCase一般并不使用,除非在受限的情境之下,因为原始的比较通常效率更高。如果与本地文化相关的比较是必不可少的,它应该被执行成基于当前的文化或者另一种特殊文化的比较。

  此外,对Equals 方法来说,字符串也通常提供了Compare方法,可以提供字符串的相对顺序信息而不仅仅中测试是否相等。这个方法可以很好适用于<, <=, >和>= 运算符,对上述讨论同样适用。

  常见误区4: 使用迭代式 (而不是声明式)的语句去操作集合

  在C# 3.0中,LINQ的引入改变了我们以往对集合对象的查询和修改操作。从这以后,你应该用LINQ去操作集合,而不是通过迭代的方式。

  一些C#的程序员甚至都不知道LINQ的存在,好在不知道的人正在逐步减少。但是还有些人误以为LINQ只用在数据库查询中,因为LINQ的关键字和SQL语句实在是太像了。

  虽然数据库的查询操作是LINQ的一个非常典型的应用,但是它同样可以应用于各种可枚举的集合对象。(如:任何实现了IEnumerable接口的对象)。举例来说,如果你有一个Account类型的数组,不要写成下面这样:

 decimal total = 0; foreach (Account account in myAccounts) { if (account.Status == "active") {
  total += account.Balance;
 }
 }

你只要这样写:

decimal total = (from account in myAccounts
    where account.Status == "active"
    select account.Balance).Sum();

  虽然这是一个很简单的例子,在有些情况下,一个单一的LINQ语句可以轻易地替换掉你代码中一个迭代循环(或嵌套循环)里的几十条语句。更少的代码通常意味着产生Bug的机会也会更少地被引入。然而,记住,在性能方面可能要权衡一下。在性能很关键的场景,尤其是你的迭代代码能够对你的集合进行假设时,LINQ做不到,所以一定要在这两种方法之间比较一下性能。

  常见错误:在LINQ语句之中没有考虑底层对象

  对于处理抽象操纵集合任务,LINQ无疑是庞大的。无论他们是在内存的对象,数据库表,或者XML文档。在如此一个完美世界之中,你不需要知道底层对象。然而在这儿的错误是假设我们生活在一个完美世界之中。事实上,相同的LINQ语句能返回不同的结果,当在精确的相同数据上执行时,如果该数据碰巧在一个不同的格式之中。

  例如,请考虑下面的语句:

decimal total=(from accout in myaccouts
where accout.status==‘active"
     select accout .Balance).sum();

  想象一下,该对象之一的账号会发生什么。状态等于“有效的”(注意大写A)?

  好吧,如果myaccout是Dbset的对象。(默认设置了不同区分大小写的配置),where表达式仍会匹配该元素。然而,如果myaccout是在内存阵列之中,那么它将不匹配,因此将产生不同的总的结果。

  等一会,在我们之前讨论过的字符串比较中, 我们看见 == 操作符扮演的角色就是简单的比较. 所以,为什么在这个条件下, == 表现出的是另外的一个形式呢 ?

  答案是,当在LINQ语句中的基础对象都引用到SQL表中的数据(如与在这个例子中,在实体框架为DbSet的对象的情况下),该语句被转换成一个T-SQL语句。然后遵循的T-SQL的规则,而不是C#的规则,所以在上述情况下的比较结束是不区分大小写的。

  一般情况下,即使LINQ是一个有益的和一致的方式来查询对象的集合,在现实中你还需要知道你的语句是否会被翻译成什么比C#的引擎或者是其他表达,来确保您的代码的行为将如预期在运行时。

  常见错误6:对扩展方法感到困惑或者被它的形式欺骗

  如同先前提到的,LINQ状态依赖于IEnumerable接口的实现对象,比如,下面的简单函数会合计帐户集合中的帐户余额:

 public decimal SumAccounts(IEnumerable<Account> myAccounts) {  return myAccounts.Sum(a => a.Balance);
 }

  在上面的代码中,myAccounts参数的类型被声明为IEnumerable<Account>,myAccounts引用了一个Sum 方法 (C# 使用类似的 “dot notation” 引用方法或者接口中的类),我们期望在IEnumerable<T>接口中定义一个Sum()方法。但是,IEnumerable<T>没有为Sum方法提供任何引用并且只有如下所示的简洁定义:

 public interface IEnumerable<out T> : IEnumerable {
  IEnumerator<T> GetEnumerator();
 }

  但是Sum方法应该定义到何处?C#是强类型的语言,因此如果Sum方法的引用是无效的,C#编译器会对其报错。我们知道它必须存在,但是应该在哪里呢?此外,LINQ提供的供查询和聚集结果所有方法在哪里定义呢?

  答案是Sum并不在IEnumerable接口内定义,而是一个定义在System.Linq.Enumerable类中的static方法(叫做“extension method”)

 namespace System.Linq {
 public static class Enumerable {  ...
  // the reference here to “this IEnumerable<TSource> source” is
  // the magic sauce that provides access to the extension method Sum
  public static decimal Sum<TSource>(this IEnumerable<TSource> source,
           Func<TSource, decimal> selector);  ...
 }
 }

  可是扩展方法和其它静态方法有什么不同之处,是什么确保我们可以在其它类访问它?

  扩展方法的显著特点是第一个形参前的this修饰符。这就是编译器知道它是一个扩展方法的“奥妙”。它所修饰的参数的类型(这个例子中的IEnumerable<TSource>)说明这个类或者接口将显得实现了这个方法。

  (另外需要指出的是,定义扩展方法的IEnumerable接口和Enumerable类的名字间的相似性没什么奇怪的。这种相似性只是随意的风格选择。)

  理解了这一点,我们可以看到上面介绍的sumAccounts方法能以下面的方式实现:

 public decimal SumAccounts(IEnumerable<Account> myAccounts) {
  return Enumerable.Sum(myAccounts, a => a.Balance);
 }

  事实上我们可能已经这样实现了这个方法,而不是问什么要有扩展方法。扩展方法本身只是C#的一个方便你无需继承、重新编译或者修改原始代码就可以给已存的在类型“添加”方法的方式。

  扩展方法通过在文件开头添加using [namespace];引入到作用域。你需要知道你要找的扩展方法所在的名字空间。如果你知道你要找的是什么,这点很容易。

  当C#编译器碰到一个对象的实例调用了一个方法,并且它在这个对象的类中找不到那个方法,它就会尝试在作用域中所有的扩展方法里找一个匹配所要求的类和方法签名的。如果找到了,它就把实例的引用当做第一个参数传给那个扩展方法,然后如果有其它参数的话,再把它们依次传入扩展方法。(如果C#编译器没有在作用域中找到相应的扩展方法,它会抛措。)

  对C#编译器来说,扩展方法是个“语法糖”,使我们能把代码写得更清晰,更易于维护(多数情况下)。显然,前提是你知道它的用法,否则,它会比较容易让人迷惑,尤其是一开始。

  应用扩展方法确实有优势,但也会让那些对它不了解或者认识不正确的开发者头疼,浪费时间。尤其是在看在线示例代码,或者其它已经写好的代码的时候。当这些代码产生编译错误(因为它调用了那些显然没在被调用类型中定义的方法),一般的倾向是考虑代码是否应用于所引用类库的其它版本,甚至是不同的类库。很多时间会被花在找新版本,或者被认为“丢失”的类库上。

  在扩展方法的名字和类中定义的方法的名字一样,只是在方法签名上有微小差异的时候,甚至那些熟悉扩展方法的开发者也偶尔犯上面的错误。很多时间会被花在寻找“不存在”的拼写错误上。

  在C#中,用扩展方法变得越来越流行。除了LINQ,在另外两个出自微软现在被广泛使用的类库Unity Application Block和Web API framework中,也应用了扩展方法,而且还有很多其它的。框架越新,用扩展方法的可能性越大。

  当然,你也可以写你自己的扩展方法。但是必须意识到虽然扩展方法看上去和其它实例方法一样被调用,但这实际只是幻。事实上,扩展方法不能访问所扩展类的私有和保护成员,所以它不能被当做传统继承的替代品。

常见错误7: 对手头上的任务使用错误的集合类型

C#提供了大量的集合类型的对象,下面只列出了其中的一部分:  

Array,ArrayList,BitArray,BitVector32,Dictionary<K,V>,HashTable,HybridDictionary,List<T>,NameValueCollection,OrderedDictionary,Queue, Queue<T>,SortedList,Stack, Stack<T>,StringCollection,StringDictionary.

  但是在有些情况下,有太多的选择和没有足够的选择一样糟糕,集合类型也是这样。数量众多的选择余地肯定可以保证是你的工作正常运转。但是你最好还是花一些时间提前搜索并了解一下集合类型,以便选择一个最适合你需要的集合类型。这最终会使你的程序性能更好,减少出错的可能。

  如果有一个集合指定的元素类型(如string或bit)和你正在操作的一样,你最好优先选择使用它。当指定对应的元素类型时,这种集合的效率更高。

  为了利用好C#中的类型安全,你最好选择使用一个泛型接口,而不是使用非泛型的借口。泛型接口中的元素类型是你在在声明对象时指定的类型,而非泛型中的元素是object类型。当使用一个非泛型的接口时,C#的编译器不能对你的代码进行类型检查。同样,当你在操作原生类型的集合时,使用非泛型的接口会导致C#对这些类型进行频繁的装箱(boxing)和拆箱(unboxing)操作。和使用指定了合适类型的泛型集合相比,这会带来很明显的性能影响。

  另一个常见的陷阱是自己去实现一个集合类型。这并不是说永远不要这样做,你可以通过使用或扩展.NET提供的一些被广泛使用的集合类型来节省大量的时间,而不是去重复造轮子。 特别是,C#的C5 Generic Collection Library 和CLI提供了很多额外的集合类型,像持久化树形数据结构,基于堆的优先级队列,哈希索引的数组列表,链表等以及更多。

  常见错误8:遗漏资源释放

  CLR 托管环境扮演了垃圾回收器的角色,所以你不需要显式释放已创建对象所占用的内存。事实上,你也不能显式释放。C#中没有与C++ delete对应的运算符或者与C语言中free()函数对应的方法。但这并不意味着你可以忽略所有的使用过的对象。许多对象类型封装了许多其它类型的系统资源(例如,磁盘文件,数据连接,网络端口等等)。保持这些资源使用状态会急剧耗尽系统的资源,削弱性能并且最终导致程序出错。

  尽管所有C#的类中都定义了析构方法,但是销毁对象(C#中也叫做终结器)可能存在的问题是你不确定它们时候会被调用。他们在未来一个不确定的时间被垃圾回收器调用(一个异步的线程,此举可能引发额外的并发)。试图避免这种由垃圾回收器中GC.Collect()方法所施加的强制限制并非一种好的编程实践,因为可能在垃圾回收线程试图回收适宜回收的对象时,在不可预知的时间内致使线程阻塞。

  这并意味着最好不要用终结器,显式释放资源并不会导致其中的任何一个后果。当你打开一个文件、网络端口或者数据连接时,当你不再使用这些资源时,你应该尽快的显式释放这些资源。

  资源泄露几乎在所有的环境中都会引发关注。但是,C#提供了一种健壮的机制使资源的使用变得简单。如果合理利用,可以大增减少泄露出现的机率。NET framework定义了一个IDisposable接口,仅由一个Dispose()构成。任何实现IDisposable的接口的对象都会在对象生命周期结束调用Dispose()方法。调用结果明确而且决定性的释放占用的资源。

  如果在一个代码段中创建并释放一个对象,却忘记调用Dispose()方法,这是不可原谅的,因为C#提供了using语句以确保无论代码以什么样的方式退出,Dispose()方法都会被调用(不管是异常,return语句,或者简单的代码段结束)。这个using和之前提到的在文件开头用来引入名字空间的一样。它有另外一个很多C#开发者都没有察觉的,完全不相关的目的,也就是确保代码退出时,对象的Dispose()方法被调用:

 using (FileStream myFile = File.OpenRead("foo.txt")) {
 myFile.Read(buffer, 0, 100);
 }

  在上面示例中使用using语句,你就可以确定myFile.Dispose()方法会在文件使用完之后被立即调用,不管Read()方法有没有抛异常。

  常见错误9: 回避异常

  C#在运行时也会强制进行类型检查。相对于像C++这样会给错误的类型转换赋一个随机值的语言来说,C#这可以使你更快的找到出错的位置。然而,程序员再一次无视了C#的这一特性。由于C#提供了两种类型检查的方式,一种会抛出异常,而另一种则不会,这很可能会使他们掉进这个“坑”里。有些程序员倾向于回避异常,并且认为不写 try/catch 语句可以节省一些代码。

  例如,下面演示了C#中进行显示类型转换的两种不同的方式:

 // 方法 1:
 // 如果 account 不能转换成 SavingAccount 会抛出异常
 SavingsAccount savingsAccount = (SavingsAccount)account;

 // 方法 2:
 // 如果不能转换,则不会抛出异常,相反,它会返回 null
 SavingsAccount savingsAccount = account as SavingsAccount;

  很明显,如果不对方法2返回的结果进行判断的话,最终很可能会产生一个 NullReferenceException 的异常,这可能会出现在稍晚些的时候,这使得问题更难追踪。对比来说,方法1会立即抛出一个 InvalidCastExceptionmaking,这样,问题的根源就很明显了。

  此外,即使你知道要对方法2的返回值进行判断,如果你发现值为空,接下来你会怎么做?在这个方法中报告错误合适吗?如果类型转换失败了你还有其他的方法去尝试吗?如果没有的话,那么抛出这个异常是唯一正确的选择,并且异常的抛出点离其发生点越近越好。

  下面的例子演示了其他一组常见的方法,一种会抛出异常,而另一种则不会:

 int.Parse();  // 如果参数无法解析会抛出异常
 int.TryParse(); // 返回bool值表示解析是否成功

 IEnumerable.First();   // 如果序列为空,则抛出异常
 IEnumerable.FirstOrDefault(); // 如果序列为空则返回 null 或默认值

  有些程序员认为“异常有害”,所以他们自然而然的认为不抛出异常的程序显得更加“高大上”。虽然在某些情况下,这种观点是正确的,但是这种观点并不适用于所有的情况。

  举个具体的例子,某些情况下当异常产生时,你有另一个可选的措施(如,默认值),那么,选用不抛出异常的方法是一个比较好的选择。在这种情况下,你最好像下面这样写:

 if (int.TryParse(myString, out myInt)) { // use myInt
 } else { // use default value
 }
  而不是这样:
 try {
 myInt = int.Parse(myString); // use myInt
 } catch (FormatException) { // use default value
 }

  但是,这并不说明 TryParse 方法更好。某些情况下适合,某些情况下则不适合。这就是为什么有两种方法供我们选择了。根据你的具体情况选择合适的方法,并记住,作为一个开发者,异常是完全可以成为你的朋友的。

常见错误10: 累积编译器警告而不处理

  这个错误并不是C#所特有的,但是在C#中这种情况却比较多,尤其是从C#编译器弃用了严格的类型检查之后。

  警告的出现是有原因的。所有C#的编译错误都表明你的代码有缺陷,同样,一些警告也是这样。这两者之间的区别在于,对于警告来说,编译器可以按照你代码的指示工作,但是,编译器发现你的代码有一点小问题,很有可能会使你的代码不能按照你的预期运行。

  一个常见的例子是,你修改了你的代码,并移除了对某些变量的使用,但是,你忘了移除该变量的声明。程序可以很好的运行,但是编译器会提示有未使用的变量。程序可以很好的运行使得一些程序员不去修复警告。更有甚者,有些程序员很好的利用了Visual Studio中“错误列表”窗口的隐藏警告的功能,很容易的就把警告过滤了,以便专注于错误。不用多长时间,就会积累一堆警告,这些警告都被“惬意”的忽略了(更糟的是,隐藏掉了)。

  但是,如果你忽略掉这一类的警告,类似于下面这个例子迟早会出现在你的代码中。

 class Account {

  int myId;  int Id; // 编译器已经警告过了,但是你不听

  // Constructor
  Account(int id) {   this.myId = Id;  // OOPS!
  }

 }

  再加上使用了编辑器的智能感知的功能,这种错误就很有可能发生。

  现在,你的代码中有了一个严重的错误(但是编译器只是输出了一个警告,其原因已经解释过),这会浪费你大量的时间去查找这错误,具体情况由你的程序复杂程度决定。如果你一开始就注意到了这个警告,你只需要5秒钟就可以修改掉,从而避免这个问题。

  记住,如果你仔细看的话,你会发现,C#编译器给了你很多关于你程序健壮性的有用的信息。不要忽略警告。你只需花几秒钟的时间就可以修复它们,当出现的时候就去修复它,这可以为你节省很多时间。试着为自己培养一种“洁癖”,让Visual Studio 的“错误窗口”一直显示“0错误, 0警告”,一旦出现警告就感觉不舒服,然后即刻把警告修复掉。

  当然了,任何规则都有例外。所以,有些时候,虽然你的代码在编译器看来是有点问题的,但是这正是你想要的。在这种很少见的情况下,你最好使用 #pragma warning disable [warning id] 把引发警告的代码包裹起来,而且只包裹警告ID对应的代码。这会且只会压制对应的警告,所以当有新的警告产生的时候,你还是会知道的。.

  C#是一门强大的并且很灵活的语言,它有很多机制和语言规范来显著的提高你的生产力。和其他语言一样,如果对它能力的了解有限,这很可能会给你带来阻碍,而不是好处。正如一句谚语所说的那样“knowing enough to be dangerous”(译者注:意思是自以为已经了解足够了,可以做某事了,但其实不是)。

  熟悉C#的一些关键的细微之处,像本文中所提到的那些(但不限于这些),可以帮助我们更好的去使用语言,从而避免一些常见的陷阱。

(0)

相关推荐

  • C# yield在WCF中的错误使用(二)

    昨天写了<yield在WCF中的错误使用--99%的开发人员都有可能犯的错误[上篇]>,引起了一些讨论.关于yield关键字这个语法糖背后的原理(C#编译器将它翻译成什么)其实挺简单,虽然有时候因为误用它会导致一些问题,但是它本无过错.接下来,我们通过这篇短文简单地谈谈我所理解的yield. 目录 一.先看一个简单的例子 二.了解本质,只需要看看yield最终编译成什么 三.回到WCF的例子 一.先看一个简单的例子 我们现在看一个简单的例子.我们在一个Console应用中编写了如下一段简单的程

  • C# FTP,GetResponse(),远程服务器返回错误

    FtpWebRequest类实现ftp功能的一般过程 1.创建一个FtpWebRequest对象,指向ftp服务器的uri 2.设置ftp的执行方法(上传,下载等) 3.给FtpWebRequest对象设置属性(是否支持ssl,是否使用二进制传输等) 4.设置登录验证(用户名,密码) 5.执行请求 6.接收相应流(如果需要的话) 7.如果没有打开的流,则关闭ftp请求 其中一些重要的属性如下: ·Credentials - 指定登录ftp服务器的用户名和密码. ·KeepAlive - 指定连接

  • C#匿名方法与Delegate类型转换错误分析

    本文实例分析了C#匿名方法与Delegate类型转换错误.分享给大家供大家参考.具体分析如下: 问题描述 C#2.0出现了匿名方法, 这在一定程度上节省了我们维护代码上下文的精力, 也不需要思考为某个方法取什么名字比较合适. 在FCL的一些方法中要求传入一个Delegate类型的参数, 比如Control.Invoke或者Control.BeginInvoke方法: 复制代码 代码如下: public object Invoke(Delegate method); public IAsyncRe

  • C#新手常犯的错误汇总

    本文所述为C#新手常犯的错误,但是实际上很多有经验的程序员也经常犯这些错误,对此特别整理了一下,供大家参考.具体如下: 1.遍历List的错误,比如如下代码: List<String> strList =newList<String> for(int i =0; i<strList.Count; i++) { strList.RemoveAt(i); } 这段代码看上去是删除了所有元素,实际上每次调用RemoveAt方法会导致List元素索引重排,最后导致元素没有完全删除.

  • C#遍历文件夹后上传文件夹中所有文件错误案例分析

    asp.net是没有直接选取文件夹的控件的,我也不知道,如果大家有的话可以一起交流下.后来我想着应该有三种方法: ①先将文件夹压缩后上传服务器,然后再服务器上解压: ②获得文件夹名及目录,然后遍历文件夹下面的文件以及子文件夹,循环上传: ③是使用AcitiveX控件. 那我果断就先通过上传对话框获得文件夹名和文件夹所在的系统文件路径,可是接下来就错愕了,一开始是想使用javascript遍历文件夹的 1  var fso = new ActiveXObject("Scripting.FileSy

  • C# 如何获取出错的错误所在行数信息 原创

    一.直接输出 ex.StackTrace 代码如下 复制代码 代码如下: try {     int num = Convert.ToInt32("");  //这一行会报错 }catch(Exception ex) {     MessageBox.Show(ex.StackTrace); } 显示的信息比较多,如下图: 二.使用 ex.StackTrace.IndexOf("行号") 处理一下 复制代码 代码如下: try {     int num = Con

  • C# yield在WCF中的错误用法(一)

    在定义API的时候,对于一些返回集合对象的方法,很多人喜欢将返回类型定义成IEnumerable<T>,这本没有什么问题.这里要说的是另一个问题:对于返回类型为IEnumerable<T>的方法来说,我们可以使用yield return的方式来输出返回集合的元素.但是如果我们不了解yield 关键字背后的实现机制,很有可能造成很大的问题. 这是一个WCF相关的问题,我想99%的人都有可能会犯这样的错误--即使你对yield了解得非常透彻.闲话少说,我们通过一个简单的实例来说明这个问

  • C#操作FTP出现500错误解决办法

    在网上也没有找到好的解决方案,于是自己研究了下给解决了,分享给大家,希望对大家能有所帮助. 一.异常信息 这种情况是因为FTP设置的默认目录引发的,以我现在的项目为例,在程序中我要访问的路径为 ftp://192.168.0.225/2007/35/0037/00001/1.jpg 这个路径,但服务器上设置的默认路径为 E:\pmserver\FTPServer\FTPSERVER , 我们打开相应的ftp目录看下,这个目录下面并没有我要访问的2007这个文件夹 那2007这个文件夹在什么位置呢

  • C#程序员最易犯的编程错误

    本文介绍了10种最常见的编程错误,或是C#程序员要避免的陷阱. 常见错误1: 像使用值一样使用参考或过来用 C++以及许多其他语言的程序员习惯于控制他们分配给变量的值是否为简易的值或现有对象的引用.在C#中呢,这将由写该对象的程序员决定,而不是由实例化该对象并对它进行变量赋值的程序员决定.这是新手C#程序员们的共同"问题". 如果你不知道你正在使用的对象是否是值类型或引用类型,你可能会遇到一些惊喜.例如: Point point1 = new Point(20, 30); Point

  • Go程序员踩过的defer坑错误处理

    目录 前言 一.简单的例子 二.一定不要在 for 循环中使用 defer 语句 三.定义函数时就不要使用命名返回值 四.defer 表达式的函数如果在 panic 后面,则这个函数无法被执行. 五.执行顺序 五.捕获异常执行顺序 六.函数执行顺序 七.外部函数捕获异常执行顺序 八.recover 的返回值问题 前言 先声明:我被坑过. 之前写 Go 专栏时,写过一篇文章:Go 专栏|错误处理:defer,panic 和 recover.有小伙伴留言说:道理都懂,但还是不知道怎么用,而且还总出现

  • PHP程序员最常犯的11个MySQL错误小结

    对于很多新手们来说,使用PHP可以在短短几个小时之内轻松地写出具有特定功能的代码.但是,构建一个稳定可靠的数据库却需要花上一些时日和相关技能.下面列举了我曾经犯过的最严重的11个MySQL相关的错误(有些同样也反映在其他语言/数据库的使用上)... 1.使用MyISAM而不是InnoDB MySQL有很多数据库引擎,但是你最可能碰到的就是MyISAM和InnoDB. MySQL默认使用的是MyISAM.但是,很多情况下这都是一个很糟糕的选择,除非你在创建一个非常简单抑或实验性的数据库.外键约束或

  • 9条PHP编程小知识及易犯的小错误

    变量声明 如果在一条语句中声明一个变量,如下所示:$var='value';编译器首先会求出语句右半部分的值,恰恰正是语句的这一部分常常会引发错误.如果使用的语法不正确,就会出现解析错误. 解析错误 例如,Parse error:解析错误,unexpected T_WHILE in c:program filesapache groupapachehtdocsscript.php on line 19每次确定了前一错误时,解析错误一个接一个地不断出现,PHP在第一个解析错误之后就停止执行脚本.而

  • Java 程序员容易犯的10个SQL错误

    Java程序员编程时需要混合面向对象思维和一般命令式编程的方法,能否完美的将两者结合起来完全得依靠编程人员的水准: 技能(任何人都能容易学会命令式编程) 模式(有些人用"模式-模式",举个例子,模式可以应用到任何地方,而且都可以归为某一类模式) 心境(首先,要写个好的面向对象程序是比命令式程序难的多,你得花费一些功夫) 但当Java程序员写SQL语句时,一切都不一样了.SQL是说明性语言而非面向对象或是命令式编程语言.在SQL中要写个查询语句是很简单的.但在Java里类似的语句却不容易

  • 成为好程序员必须避免的5个坏习惯

    当你开始成为一个程序员的时候,在编程的时候很容易陷入下面所述的一些坏习惯. 作为一名程序员,犯错误不可避免,这是你学习编程课程中的一部分.在你的职业生涯中你会犯很多错误 – 有的特别.有的普遍 – 通过这些错误你可以学习如何避免在将来再犯同样的错误. 但是如果你是一个初学者,你犯的错误可能会比其他人更频繁.那么如何才能避免大部分程序员每天都犯的这些普遍的错误呢? 想要避免错误,就要对它有所了解.这也是为什么我要和大家分享一些在我们的程序员生涯中阻碍我们成长的普遍错误. 在开始之前,你可能想知道为

  • java程序员常见的sql错误

    前言 你可能看到Java程序员每周的工作是编码开发一个可伸缩的Web应用程序,或创建一个动态的网站,或者开发高效的电子商务产品页面,也可能是开发一个Android应用程序等等.但是,即使他们致力于不同的项目,却往往都有一个共同点,那就是编程! 他们的职业要求长时间的工作来积累更多的编程知识.Java程序员还需要了解项目的需求.设计和开发一个属于自己的原型项目.为了使自己始终跟随行业变换的脚步,他们还必须具备其他语言的基础知识,比如HTML.SQL等等. Java程序员的职业生涯并不是一帆风顺的,

  • 让程序员都费解的10大编程语言特性

    每种语言都有自己的独到之处,或奇特的语法,或不常见的函数,或非标准的执行方式.因此,不论新丁还是老手,看着某个特性会突然醉了.文中总结了10个经常被提及的"奇异"特性. 1. Javascript: + 是一个连接符 问题描述: 在JS中,+ 号用在数字间,可以用作常规加法:但如果遇上字符,又可作为字符连接符.例如:'1'+ 1的结果是11. 成因分析: 根本性原因是JS属于弱类型语言.比方说Python,同样地使用+ 号作为字符连接符,但由于它是强类型语言,一旦发现一个字符与一个整数

  • 十个Python程序员易犯的错误

    常见错误1:错误地将表达式作为函数的默认参数 在Python中,我们可以为函数的某个参数设置默认值,使该参数成为可选参数.虽然这是一个很好的语言特性,但是当默认值是可变类型时,也会导致一些令人困惑的情况.我们来看看下面这个Python函数定义: >>> def foo(bar=[]): # bar是可选参数,如果没有提供bar的值,则默认为[], ... bar.append("baz") # 但是稍后我们会看到这行代码会出现问题. ... return bar Py

  • 一个30多年编程经验的程序员总结

    在我30多年的程序员生涯里,我学到了不少有用的东西.下面是我这些年积累的经验精华.我常常想,如果以前能有人在这些经验上指点一二,我相信我现在会站得更高. 1.客户在接触到产品之后,才会真正明白自己的需求. 这是我在我的第一份工作上面学来的.只有当我们给客户展示产品的时候,他们才会意识到哪些是必须的.给出一个功能性原型设计远远比一张长长的文字表格要好. 2.只要有充足的时间,所有安全防御系统都将失败. 安全防御现如今是全世界都在关注的大课题.大挑战.我们必须时时刻刻积极完善它,因为黑客只要有一次成

随机推荐