作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
帕特里克·莱德的头像

帕特里克·赖德

Pat (BMath/CS)帮助创建了VB 1.0和后来的 .. NET平台,同时在微软工作. 自2000年以来,他一直专注于全栈项目.

以前在

Microsoft
Share

关于C Sharp

C# 是针对微软的几种语言之一 公共语言运行库 (CLR). 针对CLR的语言可以从以下特性中获益 跨语言集成和异常处理、增强的安全性 简化了组件交互、调试和分析的模型 services. 在今天的CLR语言中,c#是使用最广泛的 复杂的专业 开发项目 针对Windows的 桌面、移动或服务器环境.

c#是一种面向对象的强类型语言. 严格类型 在c#中进行检查,无论是在编译时还是在运行时,结果都是大多数 尽可能早地报告典型的c#编程错误 他们的位置定位得相当准确. 这可以节省很多时间 C - Sharp程序设计, 与追踪令人费解的错误的原因相比,这些错误可能在错误操作发生很久之后才发生 放置在对类型的执行更自由的语言中 safety. 然而,很多c#程序员无意中(或不小心)抛出了错误 失去了这种检测的好处,从而导致了一些问题 在本c#教程中讨论.

关于这个C Sharp编程教程

本教程描述了10个最常见的c#编程错误, 或者需要避免的问题, 并为他们提供帮助.

虽然本文中讨论的大多数错误都是c#特有的, 其中一些还与其他针对CLR或使用 框架类库 (FCL).

c#编程常见错误#1:像使用值一样使用引用,反之亦然

c++和许多其他语言的程序员都习惯于使用 控制它们分配给变量的值是否是简单的值 或者是对现有对象的引用. 然而,在C - Sharp编程中,这个决定是 由编写对象的程序员制作,而不是由 实例化对象并将其赋值给一个变量. 这是一种常见的 对于那些试图学习c#编程的人来说,“抓到你了”.

如果你不知道你正在使用的对象是值类型还是 引用类型,你可能会遇到一些意外. 例如:

      Point Point = new Point(20,30);
      点point2 = point1;
      point2.X = 50;
      Console.WriteLine (point1.X);       // 20 (does this surprise you?)
      Console.WriteLine(卷帘窗.X);       // 50
      
      钢笔钢笔1 =新钢笔(颜色.Black);
      钢笔钢笔2 =钢笔1;
      pen2.颜色=颜色.Blue;
      Console.WriteLine (pen1.Color);     // Blue (or does this surprise you?)
      Console.WriteLine (pen2.Color);     // Blue

如你所见,两个 Point and Pen 对象以完全相同的方式创建,但是 point1 不变当新 X 赋给的坐标值 point2的值 pen1 was 当分配新颜色时修改 pen2. 因此,我们可以 deduce that point1 and point2 每个都包含它们自己的a的副本 Point 对象,而 pen1 and pen2 包含对相同的引用 Pen object. 但不做这个实验,我们怎么知道呢?

答案是查看对象类型的定义 在Visual Studio中可以通过将光标放置在 选择对象类型,按F12):

      公共结构点{ ... } //定义一个" value "类型
      公共类Pen { ... } //定义一个"引用"类型

如上所示,在c#编程中, struct 关键字用于定义值 键入,同时 class 关键字用于定义引用类型. For 那些有c++背景的人,他们被误导了 c++和c#关键字之间有许多相似之处 行为可能会让你感到惊讶,你可能会向c#教程寻求帮助.

如果你要依赖于一些不同于值的行为 以及引用类型——比如将对象作为方法传递的能力 参数,并让该方法改变对象的状态——make 确保您正在处理正确类型的对象,以避免c#编程问题.

常见c#编程错误#2:误解未初始化变量的默认值

在c#中,值类型不能为空. 根据定义,值类型有 值,甚至值类型的未初始化变量都必须有 value. 这被称为该类型的默认值. 这导致 当检查变量是否为时,通常会出现以下意外结果 未初始化:

      类程序{
          静态点point1;
          静态笔;
          静态void Main(string[] args) {
              Console.WriteLine (pen1 == null);      // True
              Console.WriteLine (point1 == null);    // False (huh?)
          }
      }

Why isn’t point1 null? 答案是 Point 是值类型,而 的默认值。 Point (0,0)不为空吗. 没有认识到这一点 在c#中很容易犯(也是很常见的)错误.

许多(但不是全部)值类型都有 IsEmpty 属性,你可以 检查它是否等于默认值:

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

当你检查一个变量是否被初始化时, 确保您知道该类型的未初始化变量的值 默认情况下,不依赖于它为空吗..

常见c#编程错误#3:使用不正确或未指定的字符串比较方法

c#中有许多不同的比较字符串的方法.

尽管许多程序员使用 == 用于字符串比较的操作符 实际上是其中之一 least 主要是可取的方法 因为它没有在代码中明确指定的类型 比较是需要的.

更确切地说,在c#编程中测试字符串是否相等的首选方法是使用 Equals method:

      public bool =(字符串值);
      public bool Equals(string value, StringComparison, 比较Type);

第一个方法签名(i.e.,没有 比较Type 参数), 实际上和用 == 操作,但有好处 显式地应用于字符串. 它执行顺序比较 它基本上是一个字节一个字节的比较. In many 这正是您想要的比较类型,特别是当 比较以编程方式设置值的字符串,例如file 名称、环境变量、属性等. 在这些情况下,只要 作为序数比较确实是比较的正确类型 这种情况下,使用 Equals 方法,不带 比较Type 是不是有人读了代码却不知道是什么类型 你所做的比较.

Using the Equals 方法签名,该签名包含 比较Type every 当你比较字符串的时候,不仅会让你的代码更清晰, 它会让你明确地思考你要进行哪种比较 需要做. 这是一件值得去做的事,因为即使是英语 可能没有提供序数和 对文化敏感的比较,其他语言提供了很多 忽略其他语言的可能性是在向一个 在这条路上有很多潜在的错误. 例如:

      字符串s = "strasse";
      
      //输出False:
      Console.WriteLine (s == "straße");
      Console.WriteLine (s.=(“箍ße "));
      Console.WriteLine (s.Equals(”接受StringComparison箍ße”.序数));
      Console.WriteLine (s.Equals(”接受StringComparison箍ße”.CurrentCulture));        
      Console.WriteLine (s.Equals(”接受StringComparison箍ße”.OrdinalIgnoreCase));
      
      //输出True:
      Console.WriteLine (s.Equals(”接受StringComparison箍ße”.CurrentCulture));
      Console.WriteLine (s.Equals(”接受StringComparison箍ße”.CurrentCultureIgnoreCase));

最安全的做法是始终提供 比较Type 参数 the Equals method. 以下是一些基本准则:

  • 比较用户输入的字符串时, 或显示给用户, 使用区域性敏感的比较(CurrentCulture or CurrentCultureIgnoreCase).
  • 在比较程序化字符串时,使用顺序比较(Ordinal or OrdinalIgnoreCase).
  • InvariantCulture and InvariantCultureIgnoreCase 一般不使用,除非在非常有限的情况下, 因为序数比较更有效. 如果有必要进行文化意识的比较, 它通常应该针对当前的文化或另一个特定的文化进行.

除了 Equals 方法,字符串还提供 Compare 方法,该方法提供有关的相对顺序的信息 字符串,而不仅仅是一个相等的测试. 这种方法更可取 to the <, <=, > and >= 操作符,原因与讨论的相同 以避免c#问题.

常见c#编程错误#4:使用迭代(而不是声明式)语句来操作集合

In C# 3.0,加法 综合语言查询 (LINQ)永远地改变了查询集合的方式 操纵. 从那时起,如果你使用迭代语句来 在处理集合时,你没有在应该使用LINQ的时候使用 have.

一些c#程序员甚至不知道LINQ的存在,但是 幸运的是,这个数字正变得越来越小. 许多人仍然 但是,我认为这是因为LINQ关键字与 SQL语句,它只在查询数据库的代码中使用.

虽然数据库查询是LINQ语句的一个非常普遍的使用,但是它们 实际上可以处理任何可枚举集合(i.e.,任何物体 实现I可列举的接口). 举个例子,如果你有 数组的帐户,而不是写一个c#列表foreach:

      十进制总数= 0;
      foreach (myAccounts中的帐户){
        如果帐户.状态== "active") {
          合计+=账户.Balance;
        }
      }

你可以这样写:

      decimal total =(来自myAccounts中的帐户)
                       在帐户.状态== "active"
                       选择账户.Balance).Sum();

虽然这是一个非常简单的例子,说明了如何避免这个常见的c#编程问题, 在某些情况下,单个 LINQ语句可以在迭代中轻松替换数十条语句 循环(或嵌套循环). 更少的代码意味着更少 引入bug的机会. 但是,请记住,在这里 可能是性能上的权衡. 在性能关键型 场景,特别是您的迭代代码能够实现的场景 LINQ不能做的关于集合的假设,一定要做a 两种方法的性能比较.

常见的c#编程错误#5:没有考虑LINQ语句中的底层对象

LINQ非常适合抽象操作集合的任务, 无论它们是内存对象、数据库表还是XML文档. 在一个完美的世界里,你不需要知道底层是什么 对象是. 但这里的错误是假设我们生活在一个完美的世界. 实际上,相同的LINQ语句在以下情况下可以返回不同的结果 对完全相同的数据执行,如果数据恰好在 不同的格式.

例如,考虑下面的语句:

      decimal total =(来自myAccounts中的帐户)
                       在帐户.状态== "active"
                       选择账户.Balance).Sum();

如果其中一个对象的 account.Status 等于“活动”(注 A)大写的;? Well, if myAccounts was a DbSet 对象(使用 默认不区分大小写的配置) where 表达会 仍然匹配那个元素. 然而,如果 myAccounts 是在内存中吗 数组时,它将不匹配,因此将对total产生不同的结果.

但是等一下. 当我们之前讨论字符串比较时,我们 看到了 == 运算符执行字符串的顺序比较. So 为什么在这种情况下是 == 执行不区分大小写的操作符 比较?

答案是,当LINQ语句中的底层对象是 对SQL表数据的引用(与实体框架的情况一样) DbSet对象(本例中为DbSet对象),语句被转换为T-SQL statement. 操作符遵循T-SQL编程规则,而不是c#编程规则 上述情况下的比较结果是不区分大小写的.

一般来说,尽管LINQ是一种有用且一致的查询方式 对象的集合,在现实中你还是需要知道是否 语句将被转换为c#以外的语言 以确保您的代码的行为将是预期的 runtime.

常见的c#编程错误#6:被扩展方法弄糊涂或欺骗

如前所述,LINQ语句适用于任何实现的对象 I可列举的. 例如,下面的简单函数将把 任何帐户集合的余额:

      public decimal SumAccounts(I可列举的 myAccounts) {
          返回myAccounts.Sum(a => a.平衡);
      }

在上面的代码中,myAccounts参数的类型声明为 I可列举的. Since myAccounts 引用一个 Sum 方法(c# 使用熟悉的“点符号”来引用类或类中的方法 接口),我们希望看到一个方法被调用 Sum() 关于定义 of the I可列举的 interface. 然而,的定义 I可列举的,没有提到任何人 Sum 方法和简单的外观 是这样的:

      public interface I可列举的 : I可列举的 {
          IEnumerator GetEnumerator();
      }

那么。 Sum() 方法定义? c#是强类型的,所以如果 参考 Sum 方法无效,则c#编译器将 当然要将其标记为错误. 因此我们知道它一定存在, but where? 此外,所有其他方法的定义在哪里 LINQ为查询或聚合这些集合所提供的功能?

答案是 Sum() 方法不是定义在 I可列举的 interface. 相反,它是一个静态方法(称为an) 类上定义的“扩展方法”) System.Linq.可列举的 class:

      名称空间系统.Linq {
        公共静态类可列举的 {
          ...
          // the reference here to “this I可列举的 source” is
          //提供对扩展方法Sum的访问
          public static decimal Sum(this I可列举的 source,
                                             Func selector);
          ...
        }
      }

那么扩展方法与其他静态方法的区别是什么呢 是什么让我们可以在其他课程中使用它?

扩展方法的显著特征是 this 第一个参数的修饰符. 这就是我的“魔力” 将其作为扩展方法标识给编译器. 的类型 参数进行修改(在本例中) I可列举的)表示 类或接口,然后将出现实现此方法.

(顺便说一句,这两者的相似性并没有什么神奇之处 的名字 I可列举的 的接口和名称 可列举的 在其上定义扩展方法的. This 相似性只是一种随意的风格选择.)

有了这样的理解,我们也可以看到 sumAccounts function 我们上面介绍的方法可以这样实现:

      public decimal SumAccounts(I可列举的 myAccounts) {
          返回可列举的.Sum(myAccounts, a => a.平衡);
      }

我们可以用这种方式实现它的事实反而引发了 为什么要有扩展方法? 扩展方法 本质上是c#编程语言的一种便利,使您能够在不创建新的派生类型的情况下向现有类型“添加”方法, 重新编译, 或者以其他方式修改原始类型.

将扩展方法纳入范围 使用[名称]; 语句在文件的顶部. 你需要知道是哪一个 c#名称空间包含您正在寻找的扩展方法,但那是 一旦你知道你在寻找什么,就很容易确定.

类实例的方法调用时,c#编译器会遇到 对象,并且不查找在引用对象上定义的方法 类,然后查看范围内的所有扩展方法 尝试找到一个与所需的方法签名和匹配的 class. 如果找到一个,它将把实例引用作为第一个传递 参数赋给该扩展方法,然后将其余参数if 将作为后续参数传递给扩展方法. (如果c#编译器没有找到任何相应的扩展方法 在作用域中,它将抛出错误.)

扩展方法是“语法糖”的一个例子 c#编译器,它允许我们编写(通常)更清晰的代码 更容易维护. 也就是说,如果你意识到他们的 usage. 否则,它可能会有点令人困惑,尤其是在开始的时候.

虽然使用扩展方法肯定有优点,但是它们 会导致问题,并呼吁c#编程帮助那些没有c#编程能力的开发人员 意识到它们或者没有正确理解它们. 这一点尤其正确 在查看在线代码示例或任何其他预先编写的代码时. 当这样的代码产生编译器错误时(因为它调用了方法) (显然没有在调用它们的类上定义) 倾向于认为代码适用于不同版本的 库,或者到一个完全不同的库. 很多时候都可以 花时间寻找一个新的版本,或者幻影“丢失的图书馆”,那 不存在.

即使是熟悉扩展方法的开发人员也会陷入困境 偶尔,当对象上有一个同名的方法时, 但是它的方法特征与 扩展方法. 很多时间都浪费在找错字上了 错误是不存在的.

c#库中扩展方法的使用越来越多 prevalent. 除了LINQ,还有 Unity应用程序块 and the Web API框架 are 两个被微软广泛使用的现代库的例子 扩展方法的使用,还有很多其他的方法. The more 框架越现代,合并的可能性就越大 扩展方法.

当然,您也可以编写自己的扩展方法. Realize, 然而,虽然调用扩展方法似乎就像 常规实例方法,这只是一种错觉. In 特别地,您的扩展方法不能引用private或protected 它们所扩展的类的成员,因此不能作为 完全替代更传统的类继承.

常见c#编程错误#7:为手头的任务使用错误的集合类型

c#提供了各种各样的集合对象,包括 只是部分列表的:
Array, ArrayList, BitArray, 将来预留, Dictionary, HashTable, HybridDictionary, List, NameValueCollection, OrderedDictionary, Queue, Queue, SortedList, Stack, Stack, StringCollection, StringDictionary.

虽然在某些情况下,选择太多和选择不够一样糟糕 选择,这与集合对象不同. 的数量 可用的选项肯定对你有利. 吃一点 额外的前期时间来研究和选择最佳的收集类型 为了你的目的. 它可能会带来更好的性能和更少的 容错余地.

如果有专门针对类型的集合类型 元素(如字符串或位)倾向于使用该元素 first. 当它以a为目标时,实现通常更有效 特定类型的元素.

为了利用c#的类型安全,通常应该使用 泛型接口优于非泛型接口. 泛型的元素 接口是声明对象时指定的类型, 而非泛型接口的元素是object类型的. When 使用非泛型接口,c#编译器不能对您的 code. 同样,在处理基本值类型的集合时, 使用非泛型集合将导致重复 装箱/拆箱 of 这些类型可能会导致显著的负面性能 与适当类型的泛型集合相比的影响.

另一个常见的c#问题是编写自己的集合对象. That 并不是说它永远都不合适,而是作为一个全面的 选择为一 .. NET提供的,您可以通过 使用或扩展一个已经存在的,而不是重新发明 wheel. 特别是, c#和CLI的C5通用集合库提供了一系列“开箱即用”的附加集合。, 例如持久树数据结构, 基于堆优先级 队列、散列索引数组列表、链表等等.

常见的c#编程错误#8:忽略释放资源

CLR环境使用了一个垃圾收集器,所以您不需要这样做 显式释放为任何对象创建的内存. 事实上,你不能. 没有c++的等价物 delete 操作员或 free() function in C . 但这并不意味着你可以忽略所有的对象 在你用完之后. 许多类型的对象封装了一些 其他类型的系统资源(例如.g.,磁盘文件,数据库连接, 网络socket等.). 让这些资源开放会很快耗尽 系统资源的总数,降低性能和 最终导致程序故障.

虽然析构函数方法可以在任何c#类中定义,但问题是 用析构函数(在c#中也称为终结器)是你无法知道的 确定他们什么时候会被召唤. 他们被垃圾叫了 收集器(在单独的线程上),这可能会导致额外的 并发症)在未来一个不确定的时间. 试着得到 通过强制垃圾收集来绕过这些限制 GC.Collect() is not a c#最佳实践,因为这会阻塞线程 收集所有符合条件的对象所需的未知时间 集合.

这并不是说终结器没有好的用途,而是释放 资源的确定性不是其中之一. 相反,当你 操作文件、网络或数据库连接时,您需要 一旦使用完底层资源,就显式地释放它.

资源泄漏是几乎所有国家都关心的问题 任何环境. 然而,c# 提供一种健壮且易于使用的机制,如果 ,可以使泄漏更少发生. The .微软网络框架 定义了 IDisposable 接口,该接口仅由 Dispose() method. 任何实现了 IDisposable 预计 在对象的使用者完成时调用该方法 操纵它. 这导致显式的、确定性的释放 resources.

类的上下文中创建和处置对象 单个代码块,忘记调用基本上是不可原谅的 Dispose(),因为c#提供了 using 声明将确保 Dispose() 无论代码块如何退出(是否退出),都会被调用 异常、返回语句或简单地结束 block). 是的,这是一样的 using 语句,用于在文件的顶部包含c#名称空间. It 有第二个,完全无关的目的,许多c#开发人员 are unaware of; namely, to ensure that Dispose() 被调用 对象,当代码块退出时:

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

通过创建 using 块在上面的例子中,您肯定知道 myFile.Dispose() 你一看完文件就会叫我, 不管是否 Read() 抛出异常.

常见的c#编程错误#9:回避异常

c#在运行时继续执行类型安全. 这允许 在c#中,您可以比在c++等语言中更快地查明许多类型的错误, 错误的类型转换可能导致任意值在哪里 分配给对象的字段. 然而,再一次,程序员可以 浪费这个伟大的特性,会导致c#问题. 他们陷入这个陷阱是因为c# 提供了两种不同的做事方式,一种可以抛出一个 一个例外,另一个不会. 有些人会回避这个例外 路由,认为不需要写try/catch块可以节省它们 一些编码.

例如,在c#中执行显式类型强制转换有两种不同的方法:

      //方法1:
      //如果account不能强制转换为SavingsAccount,则抛出异常
      SavingsAccount = (SavingsAccount)账户;
      
      //方法2:
      //如果account不能强制转换为,则不抛出异常
      // SavingsAccount; will just set savingsAccount to null instead
      SavingsAccount = SavingsAccount;

使用方法2可能出现的最明显的错误是 检查返回值失败. 这可能会导致 最终的NullReferenceException,它可能会出现在许多 后来,就更难追踪到源头了 problem. 相反,方法1会立即抛出一个 InvalidCastException 使问题的根源更加 明显.

此外,即使您记得检查方法2中的返回值, 如果你发现它是空的,你会怎么做? 是方法吗? 您正在编写一个适当的位置来报告错误? Is there 如果这种方法失败了,你还可以尝试其他方法? 如果没有,那就扔一个 异常是正确的做法,所以不妨让它发生 尽可能接近问题的根源.

下面是一些其他常见方法对的例子 抛出一个异常,而另一个不会:

      int.Parse();     // throws exception if argument can’t be parsed
      int.TryParse();  // returns a bool to denote whether parse succeeded
      
      I可列举的.First();           // throws exception if sequence is empty
      I可列举的.FirstOrDefault();  // returns null/default value if sequence is empty

一些c#开发人员是如此的“反对异常”以至于他们自动地 假设不抛出异常的方法更优越. While 在某些特定的情况下,这可能是正确的,但事实并非如此 作为概括来说是正确的.

作为一个具体的例子,在您有其他选择的情况下 合法(e.g.(默认)在异常发生时采取的操作 生成,那么非异常方法可以是 合理的选择. 在这种情况下,写信可能确实更好 像这样:

      if (int.TryParse(myString, out myInt)) {
        //使用myInt
      } else {
        //使用默认值
      }

而不是:

      try {
        myInt = int.myString Parse ();
        //使用myInt
      } catch (FormatException) {
        //使用默认值
      }

然而,假设……是不正确的 TryParse 因此, 必然是“更好”的方法. 有时候是这样,有时候 it’s not. 这就是为什么有两种方法. 用正确的 对于您所处的上下文中,请记住异常当然可以是 你作为开发者的朋友.

常见c#编程错误#10:允许编译器警告累积

虽然这个问题肯定不是c#特有的,但它是特别的 因为它放弃了严格类型的好处,所以在c#编程中是令人震惊的 c#编译器提供的检查.

警告的产生是有原因的. 而所有的c#编译器错误 表示代码中的缺陷,许多警告也是如此. What 两者的区别在于,在发出警告的情况下,编译器 发出代码所代表的指令没有问题吗. Even so, 它发现你的代码有点可疑,有一个合理的 代码没有准确反映意图的可能性.

对于本c#编程教程而言,一个常见的简单示例是修改算法以消除 使用了一个你正在使用的变量,但是你忘记删除了 变量声明. 程序将完美地运行,但是编译器 将标记无用的变量声明. 事实上,这个程序 完美运行导致程序员忽略修复的原因 warning. 此外,程序员可以利用Visual Studio 功能,使他们可以很容易地隐藏警告在“错误 “列表”窗口,这样他们就可以只关注错误. 不会花很长时间的 直到出现了几十个警告,所有的警告都被幸福地忽视了 更糟糕的是,隐藏起来).

但如果你忽略了这类警告,迟早会出现类似 这很可能会出现在你的代码中:

      类帐户{
      
          int myId;
          int Id;   // compiler warned you about this, but you didn’t listen!
  
          / /构造函数
          帐户(int id) {
              this.myId = Id;     // OOPS!
          }
  
      }

以Intellisense允许我们编写代码的速度,这个错误不是 尽管看起来不太可能.

现在,您的程序中出现了严重的错误(尽管编译器已经出现了严重的错误) 只是将其标记为警告(原因已经解释过了),并且 根据程序的复杂程度,您可能会浪费大量时间 追踪这个. 你注意到这个警告了吗 首先,您可以使用一个简单的 五秒钟的修复.

记住,C Sharp编译器给了你很多有用的信息 你的代码的健壮性……如果你在听的话. 不要忽视 warnings. 它们通常只需要几秒钟就能修好,而且修好新的 当它们发生时,可以节省你的时间. 训练自己期待 Visual Studio“错误列表”窗口显示“0错误,0警告”,所以 任何警告都会让你感到不舒服,以至于不去解决它们 立即.

当然,每条规则都有例外. 因此,可能 有时候你的代码甚至会让编译器觉得有点可疑 虽然这正是你想要的. 在那些非常罕见的 情况下,使用 #pragma warning disable [warning id] 只围绕代码 它触发警告,并且只针对它触发的警告ID. 这将抑制该警告,并且仅抑制该警告,以便您可以 仍然要对新的保持警惕.

Wrap-up

c#是一种强大而灵活的语言,具有许多机制和功能 可以大大提高生产力的范例. 和任何软件一样 工具或语言,但有有限的理解或欣赏 它的能力有时更像是一种障碍,而不是 受益,使人处于谚语所说的“知足常乐”的状态 危险的”.

使用像这样的C Sharp教程来 熟悉自己 与c#的关键细微差别, 例如(但绝不限于)本文中提出的问题, 会有助于c#的优化,同时避免一些常见的语言陷阱.

关于总博客的进一步阅读:

了解基本知识

  • 什么是c#?

    c#是针对微软CLR的几种编程语言之一, 这会自动地给它带来跨语言集成和异常处理的好处, 增强的安全, a 简化了组件交互、调试和分析的模型 services.

  • c++和c#的区别是什么?

    c++和c#是两种完全不同的语言,尽管名称和语法相似. c#被设计成比c++更高级的语言, 这两种语言在细节上也采取了不同的方法,比如谁来决定参数是通过引用传递还是通过值传递.

  • 为什么要使用c#?

    使用c#有很多原因, 但是Microsoft CLR工具集的好处和大型开发人员社区是该语言的两个主要吸引力.

Tags

聘请Toptal这方面的专家.
Hire Now
帕特里克·莱德的头像
帕特里克·赖德

位于 里诺,内华达州,美国

成员自 2012年10月17日

作者简介

Pat (BMath/CS)帮助创建了VB 1.0和后来的 .. NET平台,同时在微软工作. 自2000年以来,他一直专注于全栈项目.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

以前在

Microsoft

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

Toptal开发者

加入总冠军® community.