面试必备:C#面试题及答案(一)
(基于C# 编程技术要点详解(一)文章范围)
面试题来了,这篇文章将帮助你梳理C#面试中常问的问题,以及相关技术点。
对于 相关技术点: ,参照 文章面试必备:C# 编程技术要点详解(一)、轻松应对技术拷问(细到每个版本,技术产生原因、好处、示例) ,复习相关知识点。
对于 XX总结: ,自己理解,然后用自己的话总结出来,这样印象更深刻。结合自己的理解和体会,一定能让面试官眼前一亮。
1. 问题:C#中readonly和const有什么区别?
答案:
const和readonly都是用来创建不可变的值,但它们之间有重要区别:
- const是编译时常量,在编译时就确定值,存储在程序集的元数据中;
- readonly是运行时常量,可以在声明时或构造函数中赋值;
- const只能在声明时初始化,readonly可以在声明时或构造函数中初始化;
- const默认是静态的,而readonly可以是实例成员也可以是静态成员;
- const只能用于基本数据类型、枚举或null,readonly可以用于任何引用类型。
相关技术点: C# 1.0 基础特性
毒舌总结: 记住这个区别,别再把const当成readonly的简化写法了,不然面试官会觉得你连入门都还没入门。
2. 问题:C#中的值类型和引用类型有什么区别?string是哪种类型?
答案:
值类型和引用类型的主要区别:
- 值类型(如int, struct)直接存储数据,分配在栈上;
- 引用类型(如class, string)存储对数据的引用,分配在堆上;
- 值类型赋值时会复制整个对象,引用类型赋值时只复制引用;
- 值类型不会产生垃圾回收压力,引用类型会产生GC压力。
string是引用类型,但它具有值类型的语义特征:当修改string变量时,会创建新的string对象,而不是修改原对象。
相关技术点: C# 1.0 基础特性
鸡汤总结: 理解值类型和引用类型就像理解人与人之间的关系,有些是直接的联系,有些是通过媒介的间接联系,而string就像一个善变的朋友,表面看起来一样,内心却已改变。
3. 问题:什么是泛型?它解决了什么问题?
答案:
泛型是C# 2.0引入的特性,允许在定义类、接口、委托或方法时不指定具体的数据类型,而在使用时再指定类型。
泛型解决了以下问题:
- 类型安全:编译时类型检查,避免运行时类型转换错误;
- 性能提升:避免装箱和拆箱操作;
- 代码复用:一个泛型定义可以用于多种类型;
- 消除强制类型转换:不再需要显式的类型转换。
示例:
// 旧方式
ArrayList list = new ArrayList();
list.Add(1);
int value = (int)list[0]; // 需要强制转换,运行时可能出错,除非开发者知道list中只包含int类型
// 新方式
List<int> list = new List<int>();
list.Add(1);
int value = list[0]; // 类型安全,无需强制转换
相关技术点: C# 2.0 泛型
幽默总结: 泛型就像一个"通用模板",就像你去理发店办了张万能卡,不管你是长发、短发还是卷发,都能用它来剪,而且保证不会把你剪成隔壁老王。
4. 问题:C# 3.0中LINQ是什么?它有什么优势?
答案:
LINQ(Language Integrated Query)是C# 3.0引入的语言集成查询功能,允许开发者使用类似SQL的语法查询各种数据源。
LINQ的优势:
- 统一查询语法:可以用相同的语法查询内存集合、数据库、XML等;
- 编译时检查:查询语法在编译时检查,避免运行时错误;
- 智能提示:在IDE中提供智能感知支持;
- 类型安全:返回的结果是强类型的;
- 延迟执行:查询在枚举时才执行,提高性能。
示例:
var query = from p in people
where p.Age > 18
orderby p.Name
select p;
// 或使用方法语法
var query = people.Where(p => p.Age > 18).OrderBy(p => p.Name);
相关技术点: C# 3.0 LINQ相关特性
毒舌总结: LINQ让你可以用写SQL的方式写C#代码,但别忘了你还是在写C#,不是在偷懒写存储过程。
5. 问题:C#中扩展方法是什么?如何使用?
答案:
扩展方法是C# 3.0引入的特性,允许为现有类型添加新方法而无需修改原始类型或创建新派生类型。
扩展方法的特点:
- 必须定义在静态类中;
- 方法本身必须是静态的;
- 第一个参数使用this关键字修饰,指定要扩展的类型;
- 在客户端代码中使用时,就像实例方法一样调用。
示例:
public static class StringExtensions
{
public static string ToTitleCase(this string str)
{
if (string.IsNullOrEmpty(str))
return str;
return char.ToUpper(str[0]) + str.Substring(1).ToLower();
}
}
// 使用
string name = "zhang san";
Console.WriteLine(name.ToTitleCase()); // 输出: Zhang san
相关技术点: C# 3.0 扩展方法
鸡汤总结: 扩展方法就像给人添加技能点,不需要改变人的本质,就能让他拥有新的能力,这就是程序员的"开挂人生"。
6. 问题:async/await关键字解决了什么问题?它们是如何工作的?
答案:
async/await是C# 5.0引入的异步编程模型,解决了传统异步编程复杂、容易出错的问题。
解决的问题:
- 简化异步代码编写和维护;
- 避免阻塞UI线程,提高应用响应性;
- 提高服务器应用程序的可伸缩性;
- 减少开发人员编写异步代码时的认知负担。
工作原理:
- 当方法标记为async时,编译器会生成状态机;
- 遇到await关键字时,如果等待的任务未完成,方法会立即返回;
- 当任务完成后,方法从await点继续执行。
示例:
public async Task<string> GetDataAsync()
{
await Task.Delay(2000); // 模拟耗时操作
return "数据获取完成";
}
public async void Button_Click()
{
string result = await GetDataAsync(); // 不会阻塞UI线程
Console.WriteLine(result);
}
相关技术点: C# 5.0 异步编程支持
幽默总结: async/await就像"分身术",你可以让一个任务去慢慢执行,而你的主线程可以继续处理其他事情,等任务完成了再回来汇报结果。
7. 问题:C# 6.0中的空条件运算符(?.)有什么作用?
答案:
空条件运算符(?.)是C# 6.0引入的特性,用于安全地访问可能为null的对象成员,避免NullReferenceException异常。
作用:
- 简化空引用检查代码;
- 减少NullReferenceException异常的发生;
- 提高代码的可读性和安全性;
- 减少样板代码的数量。
示例:
// 旧方式
if (person != null && person.Address != null)
{
string city = person.Address.City;
}
// 新方式
string city = person?.Address?.City;
// 更多示例
int length = person?.Name?.Length ?? 0;
int hobbyCount = person?.Hobbies?.Count ?? 0;
someDelegate?.Invoke(); // 安全调用委托
相关技术点: C# 6.0 空条件运算符
毒舌总结: 有了?.运算符,你再也不用写那些啰嗦的null检查代码了,不过别指望它能解决你代码设计上的所有问题。
8. 问题:C# 7.0中的元组(Tuples)有什么优势?
答案:
C# 7.0中的元组提供了一种简洁的方式来从方法返回多个值,并支持命名元素。
优势:
- 简化多值返回的语法;
- 提供更好的命名支持,提高代码可读性;
- 减少创建简单数据传输类的需求;
- 支持解构,使元组使用更加灵活。
示例:
// 定义和使用命名元组
public (string Name, int Age) GetPersonInfo()
{
return ("张三", 25);
}
// 使用解构
var (name, age) = GetPersonInfo();
// 丢弃不需要的值
var (firstName, _) = GetPersonInfo(); // 只关心名字,不关心年龄
相关技术点: C# 7.0 元组
鸡汤总结: 元组就像一个临时的工具箱,虽然简单但非常实用,让你在不需要复杂结构的时候也能轻松携带多个工具。
9. 问题:C# 7.0中的模式匹配是什么?如何使用?
答案:
模式匹配是C# 7.0引入的特性,允许在is表达式和switch语句中检查对象的类型并提取值。
使用方式:
- is表达式模式匹配:在类型检查的同时声明变量;
- switch模式匹配:在switch语句中使用模式进行更复杂的匹配。
示例:
// is表达式模式匹配
public void ProcessObject(object obj)
{
if (obj is string s)
{
Console.WriteLine($"字符串长度: {s.Length}");
}
else if (obj is int i && i > 0)
{
Console.WriteLine($"正整数: {i}");
}
}
// switch模式匹配
public string GetDiscount(object customer)
{
switch (customer)
{
case PremiumCustomer p when p.Years > 5:
return "20%";
case PremiumCustomer p:
return "15%";
case BasicCustomer b when b.Age > 65:
return "10%";
default:
return "5%";
}
}
相关技术点: C# 7.0 模式匹配
幽默总结: 模式匹配就像"人脸识别系统",不仅能认出你是谁,还能顺便提取你的个人信息,比保安还厉害。
10. 问题:C# 4.0中的协变和逆变是什么?
答案:
协变和逆变是C# 4.0中对泛型接口和委托的增强,允许在特定情况下进行更灵活的类型转换。
- 协变(Covariance):允许派生类型替换基类型,用out关键字标记;
- 逆变(Contravariance):允许基类型替换派生类型,用in关键字标记。
示例:
// 协变 - 允许派生类型替换基类型
IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings; // C# 4.0 之前不允许
// 逆变 - 允许基类型替换派生类型
Action<object> objectAction = obj => Console.WriteLine(obj);
Action<string> stringAction = objectAction; // C# 4.0 之前不允许
相关技术点: C# 4.0 泛型协变和逆变
毒舌总结: 协变和逆变听起来高大上,但实际使用中大部分程序员一辈子可能都用不到几次,面试官问这个更像是在炫耀自己的理论水平。
11. 问题:C# 6.0中的nameof表达式有什么作用?
答案:
nameof表达式是C# 6.0引入的特性,用于获取变量、类型或成员的名称作为字符串,提供编译时安全保障。
作用:
- 提供编译时安全的标识符名称获取;
- 减少因重命名导致的字符串不匹配错误;
- 提高代码重构的安全性;
- 简化参数验证等场景的代码。
示例:
public void SetName(string name)
{
if (name == null)
throw new ArgumentNullException(nameof(name)); // 编译时安全
}
public class Person : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged(nameof(Name)); // 重构安全
}
}
}
相关技术点: C# 6.0 nameof表达式
鸡汤总结: nameof就像代码的"身份证",不管变量名怎么改,它都能准确地告诉你原来的"名字",让代码重构变得安全无忧。
12. 问题:C# 3.0中的Lambda表达式与匿名方法有什么区别?
答案:
Lambda表达式是C# 3.0中对匿名方法的简化语法,提供了更简洁的函数定义方式。
区别:
- 语法简洁性:Lambda表达式语法更简洁;
- 类型推断:Lambda表达式支持更好的类型推断;
- 表达式体:Lambda表达式可以直接使用表达式作为函数体;
- 应用场景:Lambda表达式更适用于LINQ查询。
示例:
// 匿名方法(C# 2.0)
button.Click += delegate(object sender, EventArgs e) {
MessageBox.Show("按钮被点击");
};
// Lambda表达式(C# 3.0)
button.Click += (sender, e) => MessageBox.Show("按钮被点击");
// 用于LINQ
var evenNumbers = numbers.Where(n => n % 2 == 0);
相关技术点: C# 3.0 Lambda表达式
幽默总结: Lambda表达式就像"缩骨功",把原来需要大段代码才能表达的逻辑压缩成一行,让代码看起来更加"苗条"。
13. 问题:C# 7.0中的out变量有什么优势?
答案:
out变量是C# 7.0引入的特性,允许在方法调用时直接声明out参数变量,使代码更加简洁。
优势:
- 简化out参数的使用;
- 减少变量声明的冗余代码;
- 提高代码的可读性;
- 支持隐式类型推断。
示例:
// 旧方式
int result;
if (int.TryParse("123", out result))
{
Console.WriteLine($"解析成功: {result}");
}
// 新方式
if (int.TryParse("123", out int result))
{
Console.WriteLine($"解析成功: {result}");
}
// 使用 var 关键字
if (int.TryParse("123", out var result))
{
Console.WriteLine($"解析成功: {result}");
}
相关技术点: C# 7.0 Out变量
毒舌总结: out变量让你少写一行代码,虽然看似微不足道,但在大量使用时能显著减少代码量,至少让你的手少敲几次键盘。
14. 问题:C# 7.2中的in参数修饰符解决了什么问题?
答案:
in参数修饰符是C# 7.2引入的特性,用于以只读引用的方式传递参数,既保证安全性又提高性能。
解决的问题:
- 提高大型结构体传递的性能;
- 保证参数不会被方法修改;
- 减少不必要的数据复制。
示例:
public struct LargeStruct
{
public int Value1, Value2, Value3, Value4, Value5;
public void ProcessData(in LargeStruct other)
{
// 可以读取 other 的值
int result = this.Value1 + other.Value1;
// 不能修改 other
// other.Value1 = 10; // 编译错误
}
}
相关技术点: C# 7.2 in参数修饰符
鸡汤总结: in参数就像"保险箱",你可以查看里面的内容但不能修改,既保证了安全性又避免了复制的开销,一举两得。
15. 问题:C# 6.0中的表达式体成员是什么?适用于哪些场景?
答案:
表达式体成员是C# 6.0引入的特性,提供了一种更简洁的语法来定义只包含单个表达式的成员。
适用场景:
- 简单的只读属性;
- 只包含单个表达式的方法;
- 只读索引器;
- 运算符重载等。
示例:
public class Person
{
private string firstName;
private string lastName;
// 表达式体只读属性
public string FullName => $"{firstName} {lastName}";
// 表达式体方法
public string GetInfo() => $"姓名: {FullName}";
}
public class MathHelper
{
// 只读属性的表达式体
public double Pi => Math.PI;
public double E => Math.E;
}
相关技术点: C# 6.0 表达式体成员
幽默总结: 表达式体成员就像"一句话介绍自己",简单明了,不需要长篇大论,适合那些不想多说的简单方法和属性。
如果轻松搞定上面15题,恭喜你,算入门过关。
作为开发者,务必多实践多写多练多领悟。
更多推荐



所有评论(0)