C# 是最流行的编程语言之一,也是 .NET 开发的首选语言。因此,如果您是一名 .NET 开发人员,正在参加 .NET 面试,您将被问到有关 C# 编程的问题。以下是针对初学者和专业 C# 开发人员的 50 个最佳 C# 面试问题和答案。
21. C# 中的后期绑定和早期绑定有什么区别?
早期绑定和晚期绑定概念属于 C# 中的多态性。多态性是面向对象编程的特性,允许语言以不同的形式使用相同的名称。例如,名为 Add 的方法可以添加整数、双精度和小数。
多态我们有两种不同的类型来实现:
- 编译时也称为早期绑定或重载。
- 运行时也称为后期绑定或覆盖。
编译时多态性或早期绑定
在编译时多态性或早期绑定中,我们将使用多个名称相同但参数类型或参数数量不同的方法。 因此,我们可以在同一个类中使用相同的方法名称执行不同的任务,也称为方法重载。
请看以下示例如何做到这一点:
运行时多态性或后期绑定
运行时多态性也称为后期绑定。在运行时多态性或后期绑定中,我们可以使用具有相同签名的相同方法名称,这意味着相同的类型或数量的参数,但不能在同一个类中,因为编译器在编译时不允许这样做。因此,当实例化子类或派生类对象时,我们可以在派生类中在运行时使用该绑定。这就是我们称之为后期绑定的原因。我们必须将我的父类函数创建为部分函数,并在驱动程序或子类中使用 override 关键字将其创建为覆盖函数。
22.IEnumerable 和 IQueryable 有什么区别?
在讨论差异之前,让我们先了解一下 IEnumerable 和 IQueryable 是什么。
(1)IEnumerable
它是 System.Collections 命名空间中所有可枚举的非泛型集合(如 ArrayList、HastTable 等)的父接口。此接口的泛型版本是 IEnumerable,它是 System.Collections.Generic 命名空间中所有泛型集合类(如 List<> 等)的父接口。
(2)IQueryable
根据 MSDN,IQueryable 接口旨在由查询提供程序实现。因此,它只能由同时实现 IQueryable 的提供程序实现。如果提供程序未同时实现 IQueryable,则标准查询运算符无法在提供程序的数据源上使用。
IQueryable 接口继承了 IEnumerable 接口,因此如果它表示查询,则可以枚举该查询的结果。枚举会导致执行与 IQueryable 对象关联的表达式树。“执行表达式树”的定义特定于查询提供程序。例如,它可能涉及将表达式树转换为适合底层数据源的查询语言。调用 Execute 方法时,将执行不返回可枚举结果的查询。
比较 | IEnumerable | IQueryable |
---|---|---|
执行方式 | 延迟执行。查询在内存中执行,数据在内存中被加载后,LINQ 查询会在本地执行。适合处理内存中的数据集合。 | 延迟执行。查询在数据库中执行,数据在数据库中被加载并处理。适合处理远程数据源,例如数据库。 |
性能 | 适合小数据集,因为它将所有数据加载到内存中,处理数据的性能取决于内存的大小和处理器的速度。 | 适合大数据集,因为它将查询转换为数据库查询,在数据库服务器端执行,减少了内存的使用,提高了性能。 |
扩展性 | 使用 LINQ 扩展方法在内存中进行对象集合的操作。 | 支持创建动态查询,允许构建表达式树,在远程数据源上执行更复杂的查询。 |
用法 | 常用于内存中的集合,如 List、Array 等。 | 常用于远程数据源,如 Entity Framework 中的 DbSet。 |
23. 如果继承的接口有冲突的方法名称会发生什么?
如果我们在同一个类中实现多个具有冲突方法名称的接口,则无需定义所有方法。换句话说,我们可以说,如果同一个类中有冲突方法,则由于名称和签名相同,我们不能在同一个类中独立实现它们的主体。因此,我们必须在方法名称之前使用接口名称来消除此方法没收。让我们看一个例子:
interface testInterface1 {
void Show();
}
interface testInterface2 {
void Show();
}
class Abc: testInterface1,
testInterface2 {
void testInterface1.Show() {
Console.WriteLine("For testInterface1 !!");
}
void testInterface2.Show() {
Console.WriteLine("For testInterface2 !!");
}
}
现在看看如何在课堂上使用这些:
class Program {
static void Main(string[] args) {
testInterface1 obj1 = new Abc();
testInterface1 obj2 = new Abc();
obj1.Show();
obj2.Show();
Console.ReadLine();
}
}
输出
24. C# 中的数组是什么?
在 C# 中,数组索引从零开始。这意味着数组的第一项从第 0 个位置开始。因此,数组上最后一项的位置将等于项数之和 -1。因此,如果数组有 10 个项,则前一个第 10 个项位于第 9 个位置。
-
在 C# 中,数组可以声明为定长或动态。
-
固定长度数组:可以存储预定义数量的项目。
-
动态数组:没有预定义的大小。相反,动态数组的大小会随着向数组中添加新项而增加。您可以声明固定长度或动态的数组。您甚至可以在定义动态数组后将其更改为静态。
-
让我们看一下 C# 中数组的简单声明。以下代码片段定义了最简单的无固定大小的整数类型动态数组。
int[] int数组;
从上面的代码片段可以看出,数组的声明以数组类型开始,后跟方括号([])和数组的名称。
以下代码片段声明了一个只能存储五个项目的数组,从索引 0 开始到 4。
int[] intArray;
intArray = new int[5];
以下代码片段声明了一个可以存储从索引 0 到 99 的 100 个项目的数组。
int[] intArray;
intArray = new int[100];
25. C# 中的构造函数链是什么?
构造函数链是一种将两个或多个类连接在一起形成继承关系的方法。在构造函数链中,每个子类构造函数都通过 base 关键字隐式映射到父类构造函数,因此当您创建子类的实例时,它将调用父类的构造函数。没有它,继承就不可能实现。
26.Array.CopyTo()和Array.Clone()有什么区别?
Array.Clone() 方法创建数组的浅表副本。Array 的浅表副本仅复制 Array 的元素(无论是引用类型还是值类型),但不会复制引用所引用的对象。新 Array 中的引用指向与原始 Array 中的相同对象。
Array 类的 CopyTo() 静态方法将数组的一部分复制到另一个数组。CopyTo 方法将数组的所有元素复制到另一个一维数组。例如,清单 9 中列出的代码将整数数组的内容复制到各种对象类型。
27. 在 C# 中可以执行多个 Catch 块吗?
我们可以将多个 catch 块与一个 try 语句一起使用。这是因为每个 catch 块可以捕获不同的异常。以下代码示例显示如何使用单个 try 语句实现多个 catch 语句。
using System;
class MyClient {
public static void Main() {
int x = 0;
int div = 0;
try {
div = 100 / x;
Console.WriteLine("Not executed line");
} catch (DivideByZeroException de) {
Console.WriteLine("DivideByZeroException");
} catch (Exception ee) {
Console.WriteLine("Exception");
} finally {
Console.WriteLine("Finally Block");
}
Console.WriteLine("Result is {0}", div);
}
}
28. 什么是单例设计模式,如何在 C# 中实现它?
单例设计模式是一种创建型设计模式,可确保一个类只有一个实例,并提供对该实例的全局访问点。此外,此模式控制对象创建,将可创建的实例数限制为单个实例,该实例在整个应用程序中共享。
在典型的 Singleton 实现中,Singleton 类有一个私有构造函数来防止直接实例化,还有一个静态方法,用于返回该类的单个实例。第一次调用静态方法时,它会创建该类的新实例并将其存储在私有静态变量中。后续调用静态方法将返回同一实例。
例如,考虑一个用于管理数据库连接的 Singleton 类 DatabaseConnection。该类可以像这样实现:
public class DatabaseConnection
{
private static DatabaseConnection instance;
private DatabaseConnection() { }
public static DatabaseConnection GetInstance()
{
if (instance == null)
instance = new DatabaseConnection();
return instance;
}
// Database connection methods
public void Connect() { /* ... */ }
public void Disconnect() { /* ... */ }
}
在此实现中,GetInstance 方法用于创建或检索类的单个实例。构造函数是私有的,因此无法从类外部直接实例化该类。
单例广泛用于管理创建成本高或可用性有限的资源的应用程序,例如数据库连接、网络套接字、日志系统等。
值得注意的是,Singleton 类可能会引入潜在问题,例如全局状态和单元测试困难,因此应谨慎使用,并且仅在必要时使用。
29. Throw Exception 和 Throw 子句之间的区别
基本区别在于,Throw 异常会覆盖堆栈跟踪。很难找到抛出异常的原始代码行号。
Throw 基本上保留堆栈信息,并在引发异常时将堆栈信息添加到堆栈中。
让我们看看这意味着什么,以便更好地理解差异。我正在使用控制台应用程序轻松测试并查看两者在功能上的使用差异。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TestingThrowExceptions {
class Program {
public void ExceptionMethod() {
throw new Exception("Original Exception occurred in ExceptionMethod");
}
static void Main(string[] args) {
Program p = new Program();
try {
p.ExceptionMethod();
} catch (Exception ex) {
throw ex;
}
}
}
}
现在按 F5 键运行代码并查看会发生什么。它返回一个异常并查看堆栈跟踪。
30. C# 中的索引器是什么?
C# 引入了一个称为索引器的新概念,用于将对象视为数组。索引器在 C# 中通常称为智能数组。然而,它们并不是面向对象编程的必要组成部分。
定义索引器允许您创建充当虚拟数组的类。然后,可以使用 [] 数组访问运算符访问该类的实例。
创建索引器
< modifier > <
return type > this[argument list] {
get {
// your get block code
}
set {
// your set block code
}
}
在上面的代码中,
<修饰符>
它可以是私有的、公共的、受保护的或内部的。
<返回类型>
它可以是任何有效的 C# 类型。
31. C# 中的多播委托是什么?
Delegate 是 .NET 中的基本类型之一。Delegate 是用于在运行时创建和调用委托的类。
C# 中的委托允许开发人员将方法视为对象并从其代码中调用它们。
实现多播委托示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
delegate void MDelegate();
class DM {
static public void Display() {
Console.WriteLine("Meerut");
}
static public void print() {
Console.WriteLine("Roorkee");
}
}
class MTest {
public static void Main() {
MDelegate m1 = new MDelegate(DM.Display);
MDelegate m2 = new MDelegate(DM.print);
MDelegate m3 = m1 + m2;
MDelegate m4 = m2 + m1;
MDelegate m5 = m3 - m2;
m3();
m4();
m5();
}
}
32. C# 中相等运算符 (==) 和 Equals() 方法之间的区别
== 运算符和 Equals() 方法比较两个值类型数据项或引用类型数据项。相等运算符 () 是比较运算符,而 Equals() 方法比较字符串的内容。 运算符比较引用标识,而 Equals() 方法仅比较内容。让我们看一些例子。
在此示例中,我们将一个字符串变量分配给另一个变量。字符串是引用类型。因此,在下面的示例中,将一个字符串变量分配给另一个字符串变量,引用堆中的相同标识,并且两者具有相同的内容。因此,对于 == 运算符和 Equals() 方法,您将获得 True 输出。
using System;
namespace ComparisionExample {
class Program {
static void Main(string[] args) {
string name = "sandeep";
string myName = name;
Console.WriteLine("== operator result is {0}", name == myName);
Console.WriteLine("Equals method result is {0}", name.Equals(myName));
Console.ReadKey();
}
}
}
33. C# 中的 Is 和 As 运算符有什么区别
“is” 运算符
在 C# 语言中,我们使用“is”运算符来检查对象类型。如果两个对象属于同一类型,则返回 true;否则,返回 false。
让我们在 C# 代码中理解这一点。首先,我们声明两个类,Speaker 和 Author。
class Speaker {
public string Name {
get;
set;
}
}
class Author {
public string Name {
get;
set;
}
}
现在,让我们创建一个 Speaker 类型的对象:
var speaker = new Speaker { Name="Gaurav Kumar Arora"};
现在,让我们检查对象是否是 Speaker 类型:
var isTrue = speaker is Speaker;
在上面,我们正在检查匹配类型。所以,是的,我们的扬声器是 Speaker 类型的对象。
Console.WriteLine("speaker is of Speaker type:{0}", isTrue);
所以,结果是正确的。
但是,这里我们得到的是错误的:
var author = new Author { Name = "Gaurav Kumar Arora" };
var isTrue = speaker is Author;
Console.WriteLine("speaker is of Author type:{0}", isTrue);
因为我们的演讲者不是Author类型的对象。
“as”运算符
“as”运算符的行为类似于“is”运算符。唯一的区别是,如果两者都与该类型兼容,则返回对象。否则返回 null。
让我们在 C# 代码中理解这一点。
public static string GetAuthorName(dynamic obj)
{
Author authorObj = obj as Author;
return (authorObj != null) ? authorObj.Name : string.Empty;
}
我们有一个方法,它接受一个动态对象,如果该对象属于 Author 类型,则返回对象名称属性。
这里我们声明了两个对象:
var speaker = new Speaker { Name="Gaurav Kumar Arora"};
var author = new Author { Name = "Gaurav Kumar Arora" };
以下返回“Name”属性:
var authorName = GetAuthorName(author);
Console.WriteLine("Author name is:{0}", authorName);
它返回一个空字符串:
authorName = GetAuthorName(speaker);
Console.WriteLine("Author name is:{0}", authorName);
34. 如何在 C# 中使用 Nullable<> 类型?
可空类型是一种包含定义的数据类型或空值的数据类型。
此可空类型概念与“var”不兼容。
任何数据类型都可以借助运算符“?”声明为可空类型。
例如,以下代码将 int“i”声明为空。
int? i = null;
如上一节所述,“var”与可空类型不兼容。因此,如果您声明以下内容,则会出错。
var? i = null;
35.方法重载有哪些不同的方式?
方法重载是一种实现编译时多态性的方法,我们可以使用具有相同名称但不同签名的方法。例如,以下代码示例有一个方法卷,根据参数和返回值的数量和类型,它具有三个不同的签名。
例子
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Hello_Word {
class overloding {
public static void Main() {
Console.WriteLine(volume(10));
Console.WriteLine(volume(2.5F, 8));
Console.WriteLine(volume(100L, 75, 15));
Console.ReadLine();
}
static int volume(int x) {
return (x * x * x);
}
static double volume(float r, int h) {
return (3.14 * r * r * h);
}
static long volume(long l, int b, int h) {
return (l * b * h);
}
}
}
备注
假设我们有一个带有两个参数对象类型的方法,以及一个带有两个整型参数的同名方法,当我们使用 int 值调用该方法时,它将调用带有整型参数的方法,而不是对象类型参数的方法。
36.什么是对象池?
.NET 中的对象池允许将对象保存在内存池中,以便可以重复使用它们而无需重新创建它们。本文介绍了 .NET 中的对象池是什么以及如何在 C# 中实现对象池。
- 这是什么意思?
对象池是存放随时可用的对象的容器。每当有新对象的请求时,池管理器就会接受该请求,并通过从池中分配一个对象来满足该请求。
- 它是如何工作的?
我们将使用工厂模式来实现此目的。我们将有一个工厂方法,它将负责对象的创建。每当有对新对象的请求时,工厂方法就会查看对象池(我们使用队列对象)。如果在允许的限制内有任何可用的对象,它将返回该对象(值对象)。否则,将创建一个新对象并返回给您。
37. C# 中的泛型是什么?
泛型允许您延迟指定类或方法中编程元素的数据类型,直到在程序中使用它们为止。换句话说,泛型允许您编写可处理任何数据类型的类或方法。
您编写类或方法的规范,并使用替代参数来代替数据类型。当编译器遇到类的构造函数或方法的函数调用时,它会生成代码来处理特定的数据类型。
泛型类和方法以非泛型类和方法无法做到的方式将可重用性、类型安全性和效率结合在一起。泛型最常用于集合及其操作方法。.NET Framework 类库 2.0 版提供了一个新的命名空间 System.Collections.Generic,其中包含几个新的基于泛型的集合类。建议所有面向 .NET Framework 2.0 及更高版本的应用程序都使用新的泛型集合类,而不是旧的非泛型类和方法,例如 ArrayList。
泛型的特点
泛型是一种通过下列方式丰富你的程序的技术:
- 首先,它可以帮助您最大限度地提高代码重用率、类型安全性和性能。
- 您可以创建泛型集合类。.NET Framework 类库在 System.Collections.Generic 命名空间中包含几个新的泛型集合类。您可以使用这些泛型集合类来代替 System.Collections 命名空间中的集合类。
- 您可以创建自己的通用接口、类、方法、事件和委托。
- 您可以创建受限的泛型类,以允许访问特定数据类型的方法。
- 您可以使用反射在运行时获取有关泛型数据类型中使用的类型的信息。
38. 访问修饰符的作用是什么?
访问修饰符是用于指定成员或类型的声明可访问性的关键字。
访问修饰符是用于指定类型成员或类型本身的可访问范围的关键字。例如,公共类可供全世界访问,而内部类可能仅供程序集访问。
为什么要使用访问修饰符?
访问修饰符是面向对象编程不可或缺的一部分。访问修饰符用于实现 OOP 的封装。此外,访问修饰符还允许您定义谁有权访问或无权访问某些功能。
在 C# 中,有六种不同类型的访问修饰符:
-
public:公有访问修饰符。类及其成员可以被任何其他代码访问,不受限制。
-
private:私有访问修饰符。类及其成员只能在定义它们的类内部访问,不能被外部代码访问。
-
protected:受保护的访问修饰符。类及其成员只能在定义它们的类内部及其派生类中访问。
-
internal:内部访问修饰符。类及其成员只能在同一个程序集中访问,不能被其他程序集访问。
-
protected internal:受保护的内部访问修饰符。类及其成员可以在同一个程序集中访问,或在派生类中访问。
-
private protected:私有受保护的访问修饰符。类及其成员只能在定义它们的类内部或在同一个程序集中派生的类中访问。
39. C# 中的虚方法是什么?
虚拟方法是可以在派生类中重新定义的方法。虚拟方法在基类和派生类中都有实现。当方法的基本功能相同,但有时派生类需要更多功能时,就会使用虚拟方法。基类中会创建一个虚拟方法,可以在派生类中重写该方法。我们使用 virtual 关键字在基类中创建一个虚拟方法,然后使用 override 关键字在派生类中重写该方法。
当某个方法在基类中被声明为虚方法时,它可以在基类中定义,并且派生类可以选择是否重写该方法。重写方法还为方法提供了多种形式。因此,它也是多态性的一个例子。
当某个方法在基类中被声明为虚方法,并且在派生类中具有相同的定义时,无需在派生类中重写该方法。 但是当某个虚方法在基类和派生类中具有不同的定义时,则需要在派生类中重写该方法。
调用虚拟方法时,将检查对象的运行时类型是否有重写成员。首先,调用最底层派生类中的重写成员,如果没有派生类重写该成员,则该成员可能是原始成员。
虚拟方法
默认情况下,方法是非虚拟的。因此,我们无法覆盖非虚拟方法。
不能将虚拟修饰符与静态、抽象、私有或覆盖修饰符一起使用。
40. C# 中的 Array 和 ArrayList 有什么区别?
比较 | Array | ArrayList |
---|---|---|
类型安全性 | 数组是类型安全的。这意味着数组中的所有元素都必须是相同类型的(或者是从相同基类派生的)。 | 是类型安全的。它可以存储任何类型的对象,因为它存储的是 object 类型。 |
大小 | 数组的大小是固定的。一旦创建,不能动态调整大小。 | 是动态大小的,可以在运行时增加或减少元素。 |
性能 | 因为数组是类型安全的,并且大小固定,所以在性能上更快,尤其是在需要频繁访问元素的情况下。 | 由于 ArrayList 是非类型安全的,需要装箱和拆箱操作,因此性能稍慢,特别是当存储值类型时。 |
使用泛型替代 | 对于类型安全和性能敏感的场景,数组是一个很好的选择。 | 在泛型引入之前,ArrayList 用于处理动态和非类型安全的集合。现在推荐使用 List 来替代 ArrayList,因为它是类型安全的,并且提供了更好的性能和功能。 |