C#面试题及详细答案120道(01-10)-- 基础语法与数据类型
《前后端面试题
》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,SQL,Linux… 。
文章目录
- 一、本文面试题目录
- 1. 简述C#与.NET的关系
- 2. C#的值类型和引用类型有什么区别?各包含哪些常见类型?
- 3. 什么是装箱和拆箱?会带来什么性能影响?
- 4. string和String、int和Int32有什么区别?
- 5. 简述C#中的常量(const)和只读(readonly)的区别
- 6. 什么是 nullable 类型?如何使用?
- 7. ref和out关键字的作用及区别
- 8. params关键字的用法
- 9. C#中的访问修饰符有哪些?各自的作用范围是什么?
- 10. 简述static关键字的用法(静态类、静态方法、静态变量)
- 二、120道C#面试题目录列表
一、本文面试题目录
1. 简述C#与.NET的关系
C#是一种编程语言,而.NET是一个跨平台的开发框架,两者紧密关联但本质不同:
- C#:由微软开发的面向对象编程语言,语法简洁、类型安全,支持多种编程范式(面向对象、泛型、函数式等)。
- .NET:提供了运行时环境(CLR)、类库(FCL)和开发工具的框架,支持多种编程语言(C#、VB.NET、F#等)。
关系说明:
- C#是.NET生态中最主流的编程语言
- C#代码需编译为中间语言(IL),再由.NET的CLR(公共语言运行时)执行
- .NET为C#提供基础类库、内存管理、异常处理等核心功能
简单来说:C#是"工具",.NET是"工作台",C#依托.NET框架实现各种功能。
2. C#的值类型和引用类型有什么区别?各包含哪些常见类型?
核心区别:存储位置和内存管理方式不同
特性 | 值类型 | 引用类型 |
---|---|---|
存储位置 | 栈(Stack) | 堆(Heap) |
内存分配 | 编译时确定 | 运行时动态分配 |
赋值方式 | 复制值本身 | 复制引用地址 |
内存释放 | 超出作用域自动释放 | 由GC(垃圾回收)管理 |
常见类型:
-
值类型:
- 基本数据类型:int、float、double、bool、char
- 结构体:struct、enum
- 其他:decimal、DateTime
-
引用类型:
- 类:class
- 接口:interface
- 数组:Array
- 字符串:string(特殊引用类型,具有不可变性)
- 委托:delegate
示例代码:
// 值类型示例
int a = 10;
int b = a; // 复制值
b = 20;
Console.WriteLine(a); // 输出 10(a不受b影响)// 引用类型示例
class MyClass { public int Value; }
MyClass x = new MyClass { Value = 10 };
MyClass y = x; // 复制引用
y.Value = 20;
Console.WriteLine(x.Value); // 输出 20(x和y指向同一对象)
3. 什么是装箱和拆箱?会带来什么性能影响?
装箱(Boxing):将值类型转换为引用类型的过程
- 原理:在堆上分配内存,复制值类型的值到新分配的内存,返回引用
拆箱(Unboxing):将引用类型(已装箱的值类型)转换回值类型的过程
- 原理:检查对象是否为指定值类型的装箱形式,将堆中的值复制到栈上
示例代码:
int i = 10; // 值类型(栈上)
object obj = i; // 装箱:int -> object(堆上分配内存)
int j = (int)obj; // 拆箱:object -> int(验证类型并复制值)
性能影响:
- 装箱会导致堆内存分配和复制操作,比普通赋值慢约10-20倍
- 拆箱需要类型检查和值复制,也会消耗额外性能
- 频繁的装箱拆箱会增加GC压力,导致性能下降
优化建议:
- 使用泛型(如List)避免装箱
- 尽量在编译时确定类型,减少运行时类型转换
- 避免在循环等高频操作中进行装箱拆箱
4. string和String、int和Int32有什么区别?
本质区别:别名与实际类型的关系
-
int vs Int32:
- int是System.Int32的别名(C#关键字)
- 两者完全等价,编译后生成的IL代码相同
- 其他类似:long对应Int64,short对应Int16等
-
string vs String:
- string是System.String的别名(C#关键字)
- 功能完全一致,但使用场景略有不同:
- 声明字符串变量时推荐用string(语言关键字)
- 调用静态方法时推荐用String(类名)
示例代码:
// int与Int32等价
int a = 10;
Int32 b = 20;
int c = (Int32)30; // 类型转换完全兼容// string与String等价
string s1 = "hello";
String s2 = "world";
bool isEqual = string.Equals(s1, s2); // 推荐用string调用实例方法
bool isNullOrEmpty = String.IsNullOrEmpty(s1); // 推荐用String调用静态方法
最佳实践:
- 保持代码风格一致,避免混合使用
- 对于值类型,优先使用关键字形式(int、bool等)
- 对于字符串操作,实例方法用string,静态方法用String
5. 简述C#中的常量(const)和只读(readonly)的区别
const(常量):编译时确定值的常量,声明后不可更改
- 必须在声明时初始化
- 只能用于值类型和string
- 属于编译时常量,编译器会直接替换其值
readonly(只读):运行时确定值的常量,只能在构造函数中修改
- 可以在声明时或构造函数中初始化
- 可用于任何类型
- 属于运行时常量,保留变量特性
示例代码:
public class MyClass
{// 常量:编译时确定值public const int ConstValue = 100;// 只读字段:可在声明时初始化public readonly int ReadOnlyValue1 = 200;// 只读字段:可在构造函数中初始化public readonly int ReadOnlyValue2;public MyClass(){ReadOnlyValue2 = 300; // 合法// ConstValue = 400; // 错误:const不能在构造函数中赋值}public void MyMethod(){// ReadOnlyValue1 = 500; // 错误:只读字段不能在方法中修改}
}
主要区别表格:
特性 | const | readonly |
---|---|---|
初始化时机 | 声明时必须初始化 | 声明时或构造函数中 |
可修改性 | 完全不可修改 | 仅能在构造函数中修改 |
适用类型 | 仅值类型和string | 所有类型 |
编译处理 | 编译器直接替换值 | 保留变量引用 |
静态特性 | 隐式静态 | 可静态可实例 |
6. 什么是 nullable 类型?如何使用?
nullable类型:允许值类型(如int、bool等)接受null值的特殊类型
- 解决了值类型默认不能为null的限制
- 语法:在值类型后加
?
,如int?
等价于Nullable<int>
使用场景:
- 数据库交互(表示可空字段)
- 需区分"未赋值"和"默认值"的场景
- 可选参数或可选值
示例代码:
// 声明可空类型
int? nullableInt = null;
bool? nullableBool = true;
DateTime? nullableDate = new DateTime(2023, 1, 1);// 赋值操作
nullableInt = 100; // 可以赋值为正常值
nullableInt = null; // 也可以赋值为null// 检查是否有值
if (nullableInt.HasValue)
{int value = nullableInt.Value; // 获取值Console.WriteLine($"值为: {value}");
}
else
{Console.WriteLine("值为null");
}// 空合并运算符(??)
int result = nullableInt ?? -1; // 如果为null则使用-1
Console.WriteLine(result); // 输出 -1(因为nullableInt当前为null)// 条件运算符(?.)
int? length = nullableDate?.Day; // 安全访问,避免NullReferenceException
注意事项:
- 引用类型本身可null,无需使用nullable类型
- 可空类型之间可以相互转换
- 使用
GetValueOrDefault()
可获取值或默认值(不抛异常)
7. ref和out关键字的作用及区别
ref关键字:用于传递变量的引用,允许方法修改调用者的变量值
- 要求变量在传递前必须初始化
- 用于"传递并可能修改"已有变量
out关键字:用于传递变量的引用,强调方法必须为变量赋值
- 变量在传递前可以不初始化
- 用于"输出"多个返回值的场景
示例代码:
// ref关键字示例
int x = 10;
ModifyWithRef(ref x);
Console.WriteLine(x); // 输出 20void ModifyWithRef(ref int value)
{value *= 2; // 修改原始变量的值
}// out关键字示例
int y; // 可以不初始化
bool success = TryParseNumber("123", out y);
if (success)
{Console.WriteLine(y); // 输出 123
}bool TryParseNumber(string input, out int result)
{// 方法必须为out参数赋值if (int.TryParse(input, out result)){return true;}result = 0; // 即使失败也必须赋值return false;
}
主要区别:
特性 | ref | out |
---|---|---|
初始化要求 | 传递前必须初始化 | 传递前可未初始化 |
赋值要求 | 方法内可赋可不赋 | 方法内必须赋值 |
主要用途 | 传递并修改已有值 | 返回多个结果值 |
数据流向 | 双向(进和出) | 单向(仅出) |
使用建议:
- 需要修改已有变量时用ref
- 需要返回多个结果时用out(如TryXXX模式)
- C# 7.0+支持out变量声明简化:
TryParseNumber("123", out int y)
8. params关键字的用法
params关键字:允许方法接受数量可变的参数,这些参数被视为数组处理
- 简化了调用方传递多个参数的语法
- 每个方法只能有一个params参数,且必须是最后一个参数
使用场景:
- 不确定参数数量的方法(如字符串格式化、数学计算)
- 希望提供更简洁调用方式的API
示例代码:
// 定义带params参数的方法
public static int Sum(params int[] numbers)
{int total = 0;foreach (int num in numbers){total += num;}return total;
}// 调用方式
public static void Main()
{// 1. 传递多个参数int sum1 = Sum(1, 2, 3, 4);Console.WriteLine(sum1); // 输出 10// 2. 传递数组int[] values = { 5, 6, 7 };int sum2 = Sum(values);Console.WriteLine(sum2); // 输出 18// 3. 传递零个参数int sum3 = Sum();Console.WriteLine(sum3); // 输出 0
}// 结合其他参数使用(params必须是最后一个)
public static string FormatMessage(string format, params object[] args)
{return string.Format(format, args);
}// 调用
string message = FormatMessage("Name: {0}, Age: {1}", "Alice", 30);
注意事项:
- params参数本质是数组,方法内部按数组处理
- 避免在高性能场景中过度使用(数组分配有性能开销)
- 不能与ref或out同时使用
9. C#中的访问修饰符有哪些?各自的作用范围是什么?
C#提供5种访问修饰符,用于控制类型和成员的访问权限:
-
public(公共)
- 完全公开,任何地方都可访问
- 适用:需要被外部广泛访问的成员
-
private(私有)
- 仅在当前类或结构体内部可访问
- 适用:类内部的实现细节,默认访问级别(接口和枚举除外)
-
protected(保护)
- 在当前类及派生类中可访问
- 适用:需要被继承类使用的成员
-
internal(内部)
- 仅在当前程序集(项目)内可访问
- 适用:同一程序集内共享的类型或成员
-
protected internal(保护内部)
- 在当前程序集内或派生类中可访问(取protected和internal的并集)
- 适用:需要在程序集内或继承体系中共享的成员
示例代码:
public class MyBaseClass
{public int PublicField; // 任何地方可访问private int PrivateField; // 仅MyBaseClass内部可访问protected int ProtectedField; // MyBaseClass及派生类可访问internal int InternalField; // 同一程序集内可访问protected internal int ProtectedInternalField; // 同一程序集或派生类可访问public void PublicMethod(){// 类内部可访问所有成员PrivateField = 1;ProtectedField = 2;}
}public class MyDerivedClass : MyBaseClass
{public void AccessBaseMembers(){PublicField = 1; // 允许// PrivateField = 2; // 错误:无法访问私有成员ProtectedField = 3; // 允许(派生类)// InternalField = 4; // 允许仅当在同一程序集ProtectedInternalField = 5; // 允许(派生类)}
}public class ExternalClass
{public void AccessMembers(MyBaseClass obj){obj.PublicField = 1; // 允许// obj.PrivateField = 2; // 错误// obj.ProtectedField = 3; // 错误// obj.InternalField = 4; // 允许仅当在同一程序集// obj.ProtectedInternalField = 5; // 允许仅当在同一程序集}
}
最佳实践:
- 遵循"最小权限原则",尽量使用更严格的访问级别
- 类的字段通常设为private,通过public属性暴露
- 跨程序集共享的类型设为public,内部使用的设为internal
10. 简述static关键字的用法(静态类、静态方法、静态变量)
static关键字:用于声明属于类型本身而非实例的成员,主要有三种用法:
-
静态变量(静态字段)
- 属于类本身,所有实例共享同一变量
- 内存中只存在一份,在第一次访问类时初始化
- 常用于存储全局状态或计数器
-
静态方法
- 属于类本身,无需创建实例即可调用
- 只能访问静态成员,不能访问实例成员
- 常用于工具类方法、工厂方法等
-
静态类
- 只包含静态成员,不能实例化
- 不能被继承
- 常用于工具类(如Math、Convert)
示例代码:
// 静态类示例
public static class MathUtility
{// 静态常量public const double Pi = 3.14159;// 静态方法public static double CalculateArea(double radius){return Pi * radius * radius;}
}// 包含静态成员的普通类
public class Counter
{// 静态变量(所有实例共享)private static int _instanceCount = 0;// 实例变量(每个实例独有)private int _id;// 静态构造函数(初始化静态成员)static Counter(){Console.WriteLine("静态构造函数调用");}// 实例构造函数public Counter(){_instanceCount++;_id = _instanceCount;}// 静态方法public static int GetTotalCount(){return _instanceCount;}// 实例方法public int GetId(){return _id;}
}// 使用示例
public static void Main()
{// 调用静态类方法double area = MathUtility.CalculateArea(5);// 使用包含静态成员的类Counter c1 = new Counter();Counter c2 = new Counter();Console.WriteLine(Counter.GetTotalCount()); // 输出 2Console.WriteLine(c1.GetId()); // 输出 1Console.WriteLine(c2.GetId()); // 输出 2
}
静态构造函数特点:
- 无参数,不能有访问修饰符
- 自动调用,仅调用一次
- 用于初始化静态成员
注意事项:
- 静态成员生命周期与应用程序相同,可能导致内存占用问题
- 过度使用静态成员会增加代码耦合度,不利于测试
- 静态方法不能被重写,但可以被隐藏(使用new关键字)
二、120道C#面试题目录列表
文章序号 | C#面试题120道 |
---|---|
1 | C#面试题及详细答案120道(01-10) |
2 | C#面试题及详细答案120道(11-20) |
3 | C#面试题及详细答案120道(21-30) |
4 | C#面试题及详细答案120道(31-40) |
5 | C#面试题及详细答案120道(41-50) |
6 | C#面试题及详细答案120道(51-60) |
7 | C#面试题及详细答案120道(61-75) |
8 | C#面试题及详细答案120道(76-85) |
9 | C#面试题及详细答案120道(86-95) |
10 | C#面试题及详细答案120道(96-105) |
11 | C#面试题及详细答案120道(106-115) |
12 | C#面试题及详细答案120道(116-120) |