2019 年你需要知道的 27 个 C# 面试问题和答案

2025-05-24

2019 年你需要知道的 27 个 C# 面试问题和答案

2019 年你需要知道的 27 个 C# 面试问题和答案
如今,C# 是第四大热门编程语言,约有 31% 的开发者定期使用它。它也是 StackOverflow(使用 C# 构建)上第三大社区,拥有超过 110 万个主题。

🔴 最初发表于FullStack.Cafe - Kill Your Tech & Coding Interview

Q1:什么是对象?

主题:C#
难度:⭐

根据 MSDN 的定义,“类或结构的定义就像一份蓝图,指定了该类型可以做什么。对象基本上是根据蓝图分配和配置的一块内存。一个程序可以创建同一个类的多个对象。对象也称为实例,它们可以存储在命名变量、数组或集合中。客户端代码是使用这些变量来调用方法和访问对象公共属性的代码。在面向对象语言(例如 C#)中,典型的程序由多个动态交互的对象组成”。

对象帮助我们通过使用点来访问类或结构的成员,它们可以是字段、方法或属性。

🔗资料来源: c-sharpcorner.com

Q2:什么是托管代码或非托管代码?

主题:C#
难度:⭐⭐

  • 托管代码- 在 .NET 框架中开发的代码称为托管代码。此类代码由 CLR 在托管代码执行的帮助下直接执行。任何使用 .NET 框架编写的语言都是托管代码。
  • 非托管代码- 在 .NET 框架之外开发的代码称为非托管代码。不在 CLR 控制下运行的应用程序被称为非托管代码,某些语言(例如 C++)可用于编写此类应用程序,例如,访问操作系统的低级函数。与 VB、ASP 和 COM 代码的后台兼容性就是非托管代码的示例。

🔗资料来源: c-sharpcorner.com

Q3:什么是装箱和拆箱?

主题:C#
难度:⭐⭐

装箱和拆箱都用于类型转换,但有一些区别:

  • 装箱- 装箱是将值类型数据类型转换为对象或由此值类型实现的任何接口数据类型的过程。当 CLR 将值装箱时,意味着当 CLR 将值类型转换为对象类型时,它会将该值包装在 System.Object 中,并将其存储在应用程序域的堆区域中。

  • 拆箱- 拆箱也是一个从对象或任何已实现的接口类型中提取值类型的过程。装箱可以隐式执行,但拆箱必须通过代码显式执行。

装箱和拆箱的概念强调了 C# 类型系统的统一视图,其中任何类型的值都可以被视为对象。

🔗资料来源: c-sharpcorner.com

Q4:C# 中的泛型是什么?

主题:C#
难度:⭐⭐

泛型允许你将类或方法中编程元素的数据类型指定延迟到程序实际使用时。换句话说,泛型允许你编写一个可以处理任何数据类型的类或方法。

🔗资料来源: c-sharpcorner.com

Q5:为什么不能为接口内部的方法指定可访问性修饰符?

主题:C#
难度:⭐⭐

在接口中,我们有一些没有方法定义的虚方法。所有方法都可以在派生类中被重写。这就是为什么它们都是公共的。

🔗来源: guru99.com

Q6:C# 中的引用类型是什么?

主题:C#
难度:⭐⭐

引用类型包含变量中存储的实际数据,但包含对变量的引用。

换句话说,它们指向一个内存位置。使用多个变量,引用类型可以指向一个内存位置。如果其中一个变量更改了该内存位置中的数据,另一个变量的值也会自动反映更改。内置引用类型的示例包括:对象、动态和字符串。

🔗资料来源: tutorialspoint.com

Q7:接口和抽象类有什么区别?

主题:C#
难度:⭐⭐⭐

抽象类接口之间存在一些区别,如下所示:

  • 一个类可以实现任意数量的接口,但子类最多只能使用一个抽象类。
  • 抽象类可以有非抽象方法(具体方法),而在接口的情况下,所有方法都必须是抽象的。
  • 抽象类可以声明或使用任何变量,而接口则不允许这样做。
  • 在抽象类中,所有数据成员或函数默认都是私有的,而在接口中,所有数据成员或函数都是公共的,我们不能手动更改它们。
  • 在抽象类中,我们需要使用 abstract 关键字来声明抽象方法,而在接口中我们不需要使用它。
  • 抽象类不能用于多重继承,而接口可以用于多重继承。
  • 抽象类使用构造函数,而在接口中我们没有任何类型的构造函数。

🔗资料来源: c-sharpcorner.com

Q8:重载和覆盖有什么区别?

主题:C#
难度:⭐⭐⭐

  • 重载是指在同一范围内有多个方法,具有相同的名称但不同的签名。
//Overloading
public class test
{
    public void getStuff(int id)
    {}
    public void getStuff(string name)
    {}
}
Enter fullscreen mode Exit fullscreen mode
  • 覆盖是一种允许您更改子类中方法的功能的原则。
//Overriding
public class test
{
        public virtual void getStuff(int id)
        {
            //Get stuff default location
        }
}

public class test2 : test
{
        public override void getStuff(int id)
        {
            //base.getStuff(id);
            //or - Get stuff new location
        }
}
Enter fullscreen mode Exit fullscreen mode

🔗来源: stackoverflow.com

Q9:虚方法和抽象方法有什么区别?

主题:C#
难度:⭐⭐⭐

  • 虚方法必须始终具有默认实现。不过,它可以在派生类中被重写,但并非强制要求。可以使用override关键字进行重写。
  • 抽象方法没有实现。它位于抽象类中。派生类必须实现该抽象方法。此处可以使用override关键字,但并非必需。

🔗来源: softwaretestinghelp.com

Q10:动态类型变量和对象类型变量有什么区别?

主题:C#
难度:⭐⭐⭐

动态类型与对象类型类似,不同之处在于对象类型变量的类型检查在编译时进行,而动态类型变量的类型检查在运行时进行。

🔗资料来源: tutorialspoint.com

Q11:下面程序的输出是什么?解释你的答案。

主题:C#
难度:⭐⭐⭐

考虑:

delegate void Printer();

static void Main()
{
        List<Printer> printers = new List<Printer>();
        int i=0;
        for(; i < 10; i++)
        {
            printers.Add(delegate { Console.WriteLine(i); });
        }

        foreach (var printer in printers)
        {
            printer();
        }
}
Enter fullscreen mode Exit fullscreen mode

该程序将输出数字 10 十次。

原因如下:委托是在 for 循环中添加的,并且存储的是“引用”(或者“指针”可能更贴切)i,而不是值本身。因此,退出循环后,变量i已被设置为 10,所以在调用每个委托时,传递给所有委托的值都是 10。

🔗资料来源: toptal.com

Q12:什么是表达式树以及它们如何在 LINQ 中使用?

主题:LINQ
难度:⭐⭐⭐

表达式是一种包含表达式(本质上是代码)的数据结构。因此,它是一个树形结构,表示您可以在代码中进行的计算。然后,可以通过在一组数据上“运行”表达式树来执行这些代码片段。

在 LINQ 中,表达式树用于表示针对实现了 的数据源的结构化查询IQueryable<T>。例如,LINQ 提供程序实现了IQueryable<T>用于查询关系数据存储的接口。C# 编译器将针对此类数据源的查询编译为在运行时构建表达式树的代码。然后,查询提供程序可以遍历表达式树数据结构,并将其转换为适用于该数据源的查询语言。

🔗来源: docs.microsoft.com

Q13:什么是编组以及我们为什么需要它?

主题:C#
难度:⭐⭐⭐⭐

因为不同的语言和环境有不同的调用约定、不同的布局约定、不同的原语大小(例如 C# 中的 char 和 C 中的 char)、不同的对象创建/销毁约定以及不同的设计准则。你需要一种方法将数据从托管空间移到非托管空间可以看到和理解的地方,反之亦然。这就是编组的目的所在。

🔗来源: stackoverflow.com

Q14:方法重载有哪些不同的方式?

主题:C#
难度:⭐⭐⭐⭐

可以使用不同的参数数据类型、不同的参数顺序和不同的参数数量来重载方法。

🔗来源: guru99.com

Q15:在 C# 中,我们可以只有“try”块而没有“catch”块吗?

主题:C#
难度:⭐⭐⭐⭐

是的,我们只能有 try 块而没有 catch 块,但我们必须有 finally 块。

🔗资料来源: a4academics.com

Q16:C# 中的委托有什么用途?

主题:C#
难度:⭐⭐⭐⭐

以下是 C# 中委托的用途列表:

  • 回调机制
  • 异步处理
  • 抽象和封装方法
  • 多播

🔗资料来源: a4academics.com

Q17:C# 中可以实现多重继承吗?

主题:C#
难度:⭐⭐⭐⭐

在 C# 中,派生类只能继承一个基类。如果要继承多个基类,请使用接口。

🔗来源: stackoverflow.com

Q18:C# 中的“yield”关键字有什么用处?

主题:C#
难度:⭐⭐⭐⭐

考虑:

IEnumerable<object> FilteredList()
{
    foreach( object item in FullList )
    {
        if( IsItemInPartialList( item )
            yield return item;
    }
}
Enter fullscreen mode Exit fullscreen mode

你能解释一下这个yield关键词的作用吗?


该函数返回一个实现该IEnumerable接口的对象。如果调用函数开始对该对象进行 foreach 操作,则该函数会被再次调用,直到“yield”为止。这是 C# 2.0 中引入的语法糖。

Yield有两大用途:

  • 它有助于提供自定义迭代而无需创建临时集合。
  • 它有助于进行状态迭代。

使用的优点yield是,如果使用数据的函数只需要集合的第一个项目,则不会创建其余项目。

🔗来源: stackoverflow.com

Q19:列举 LINQ 相对于存储过程的一些优势

主题:LINQ
难度:⭐⭐⭐⭐

LINQ 相对于 sprocs 的一些优势:

  1. 类型安全:我想我们都明白这一点。
  2. 抽象:对于 LINQ-to-Entities 来说尤其如此。这种抽象还允许框架添加其他您可以轻松利用的改进。PLINQ 就是为 LINQ 添加多线程支持的一个例子。添加此支持几乎不需要修改任何代码。相比之下,编写这种仅仅调用存储过程的数据访问代码则要困难得多。
  3. 调试支持:我可以使用任何 .NET 调试器来调试查询。使用存储过程 (sprocs) 时,您无法轻松调试 SQL,而且这种体验很大程度上取决于您的数据库供应商(MS SQL Server 提供了查询分析器,但这通常还不够)。
  4. 供应商无关:LINQ 兼容大量数据库,并且支持的数据库数量只会持续增长。存储过程并不总是能够在不同数据库之间移植,这可能是因为语法或功能支持存在差异(如果数据库支持存储过程的话)。
  5. 部署:部署单个程序集比部署一组存储过程更容易。这也与第 4 条相关。
  6. 更简单:您无需学习 T-SQL 即可进行数据访问,也无需学习调用存储过程所需的数据访问 API(例如 ADO.NET)。这与第 3 点和第 4 点相关。

🔗来源: stackoverflow.com

Q20:C# 中的深拷贝或浅拷贝概念是什么?

主题:C#
难度:⭐⭐⭐⭐⭐

  • 浅拷贝是指将对象的值类型字段复制到目标对象中,并将对象的引用类型作为引用复制到目标对象中,而不是被引用的对象本身。它逐位复制类型。结果是两个实例都被克隆,并且原始实例将引用同一个对象。
  • Deep Copy用于对内部引用类型进行完整的深度复制,为此我们需要配置返回的对象MemberwiseClone()

换句话说,当一个对象连同它所引用的对象一起被复制时,就会发生深层复制。

🔗来源: social.msdn.microsoft.com

Q21:C# 中的多播委托是什么?

主题:C#
难度:⭐⭐⭐⭐⭐

委托只能调用一个封装在委托中的方法引用。某些委托可以持有并调用多个方法,这样的委托称为多播委托。多播委托又称为可组合委托,必须满足以下条件:

  • 委托的返回类型必须为 void。委托类型的任何参数都不能是委托类型,可以使用 out 关键字声明为输出参数。
  • 多播委托实例由两个委托组合而成,调用列表由两个加法运算操作数的调用列表连接而成。委托的调用顺序与它们相加的顺序一致。

实际上,C# 中的所有委托都是多播委托 (MulticastDelegates),即使它们只有一个方法作为目标。(即使是匿名函数和 lambda,即使根据定义只有一个目标,它们也是多播委托。)

考虑:

ublic partial class MainPage : PhoneApplicationPage
{
    public delegate void MyDelegate(int a, int b);
    // Constructor
    public MainPage()
    {
        InitializeComponent();

        // Multicast delegate
        MyDelegate myDel = new MyDelegate(AddNumbers);
        myDel += new MyDelegate(MultiplyNumbers);
        myDel(10, 20);
    }

    public void AddNumbers(int x, int y)
    {
        int sum = x + y;
        MessageBox.Show(sum.ToString());
    }

    public void MultiplyNumbers(int x, int y)
    {
        int mul = x * y;
        MessageBox.Show(mul.ToString());
    }

}
Enter fullscreen mode Exit fullscreen mode

🔗来源: stackoverflow.com

Q22:列举一些在 .Net 中检查相等性的不同方法

主题:C#
难度:⭐⭐⭐⭐⭐

  • ReferenceEquals() 方法 - 检查两个引用类型变量(类,而不是结构)是否指向相同的内存地址。
  • 虚拟 Equals() 方法。(System.Object)- 检查两个对象是否相等。
  • 静态 Equals() 方法 - 用于处理检查中出现空值时出现的问题。
  • IEquatable 接口中的 Equals 方法。
  • 比较运算符 == 的含义通常与 ReferenceEquals 相同,它检查两个变量是否指向相同的内存地址。问题在于,此运算符可以被重写以执行其他类型的检查。例如,在字符串中,它检查两个不同的实例是否相等。

🔗来源: stackoverflow.com

Q23:lambda 和委托之间有什么区别?

主题:C#
难度:⭐⭐⭐⭐⭐

它们实际上是两种截然不同的东西。

  • Delegate实际上是保存对方法或 lambda 的引用的变量的名称,而 lambda 是一种没有永久名称的方法。
  • Lambda与其他方法非常相似,除了几个细微的差别:
    • 普通方法是在“语句”中定义的,并且与永久名称绑定,而 lambda 是在“表达式”中“动态”定义的,并且没有永久名称。
    • Lambdas 可以与 .NET 表达式树一起使用,而方法则不能。

🔗来源: stackoverflow.com

Q24:你能解释一下 Func、Action 和 Predicate 之间的区别吗?

主题:C#
难度:⭐⭐⭐⭐⭐

  • 谓词:本质上Func<T, bool>;提出问题“指定的参数是否满足委托所代表的条件?”用于诸如此类的事情List.FindAll
  • Action:根据参数执行操作。用途非常广泛。在 LINQ 中用得不多,因为它基本上会引发副作用。
  • Func:在 LINQ 中广泛使用,通常用于转换参数,例如通过将复杂结构投影到一个属性。

🔗来源: stackoverflow.com

Q25:用 C 语言实现“Where”方法

主题:C#
难度:⭐⭐⭐⭐⭐

考虑:

public static IEnumerable<T> Where<T>(this IEnumerable<T> items, Predicate< T> prodicate)
{
  foreach(var item in items)
  {
      if (predicate(item))
      {
           // for lazy/deffer execution plus avoid temp collection defined
           yield return item;
      }
  }
}

Enter fullscreen mode Exit fullscreen mode

🔗来源: medium.com

Q26:解释一下 C# 中的弱引用是什么?

主题:C#
难度:⭐⭐⭐⭐⭐

当应用程序代码可以访问某个对象时,垃圾回收器无法回收该对象。应用程序对该对象拥有强引用。

弱引用允许垃圾回收器回收对象,同时应用程序仍可访问该对象。弱引用仅在不存在强引用的情况下,在对象被回收之前的一段不确定的时间内有效。

弱引用对于使用大量内存的对象很有用,但如果它们被垃圾回收器回收,则可以轻松地重新创建。

🔗来源: docs.microsoft.com

Q27:列举 LINQ 与 sprocs 相比的一些缺点

主题:LINQ
难度:⭐⭐⭐⭐⭐

LINQ 与 sprocs 相比的一些缺点:

  1. 网络流量:存储过程只需通过网络序列化存储过程名称和参数数据,而 LINQ 则发送整个查询。如果查询非常复杂,这种情况可能会非常糟糕。不过,LINQ 的抽象允许微软逐步改进这一点。
  2. 灵活性较差:存储过程可以充分利用数据库的功能集。LINQ 的支持更通用。这在任何类型的抽象语言(例如 C# 与汇编语言)中都很常见。
  3. 重新编译:如果您需要更改数据访问方式,则需要重新编译、版本控制并重新部署程序集。存储过程有时允许数据库管理员调整数据访问例程,而无需重新部署任何内容。

安全性和可管理性也是人们争论的话题。

  1. 安全性:例如,您可以通过限制对表的直接访问,并在存储过程上设置 ACL 来保护敏感数据。然而,使用 LINQ,您仍然可以限制对表的直接访问,而是将 ACL 设置在可更新表视图上,以达到类似的效果(假设您的数据库支持可更新视图)。
  2. 可管理性:使用视图还能让您的应用程序免受架构变更(例如表规范化)的影响,且不会造成应用程序中断。您可以更新视图,而无需更改数据访问代码。

🔗来源: stackoverflow.com

感谢🙌的阅读,祝你面试顺利!
如果喜欢,请分享给你的开发者同事!
更多 FullStack 面试问答,请访问👉 www.fullstack.cafe

文章来源:https://dev.to/fullstackcafe/60-c-interview-questions-to-get-job-offer-in-2019-1382
PREV
8 个步骤将开发人员简历回复率提高 90%
NEXT
39+ 你必须搞清楚的高级 React 面试题(已解答)(2020 年更新)