主题
C#开发工程师(面试260308)
1. 什么是 CTS、CLS 和 CLR
解答
CTS(通用类型系统):
- 定义
.NET全部数据类型与行为规范 - 统一多语言类型,保障跨语言兼容
- 核心分类:
值类型、引用类型
CLS(通用语言规范):
- 是
CTS的子集,跨语言开发最小约束 - 屏蔽语言差异(如大小写、关键字)
- 标记
[CLSCompliant]即可合规开发
CLR(公共语言运行时):
.NET核心执行引擎- 负责IL解析、JIT编译、GC回收、线程管理、安全校验
- 主流版本:
CoreCLR、Desktop CLR
csharp
// CLS 合规代码演示
using System;
[assembly: CLSCompliant(true)]
public class TypeDemo
{
// CTS 标准类型
public System.Int32 Num { get; set; }
public System.String Text { get; set; }
}2. CLR 技术和 COM 技术的比较
解答
| 特性 | CLR | COM |
|---|---|---|
| 内存管理 | GC自动回收 | 手动引用计数 |
| 跨平台 | 全平台跨端 | 仅限Windows |
| 版本控制 | 并行部署无DLL冲突 | 经典DLL Hell |
| 类型系统 | 统一CTS类型 | 独立类型库 |
| 注册机制 | 无需注册表 | 强制系统注册表注册 |
csharp
// CLR 轻量化开发示例
public class ClrDemo
{
public int Calc(int a,int b)=>a+b;
}
// 无需手动释放、注册,GC自动管理内存3. JIT 是如何工作的
解答
JIT(即时编译):CLR 核心编译机制,核心流程:
- 源码编译:
C#代码编译为IL中间语言,存入程序集 - 运行加载:程序启动加载
IL代码,不提前编译 - 即时编译:方法首次调用时,实时编译为当前
CPU机器码 - 缓存复用:编译后代码缓存,后续直接执行,提升性能
- 分层优化:
.NET新增分层编译,平衡启动速度与运行效率
csharp
public class JitTest
{
// 首次调用触发JIT编译,二次调用走缓存
public static int Run(int x)=>x*x+1;
}4. 怎么把程序放入 GAC
解答
GAC(全局程序集缓存):Windows 全局共享程序集目录
前置条件
程序集必须强名称签名
操作步骤
- 生成强名称密钥
powershell
sn -k Key.snk- 项目配置签名
xml
<PropertyGroup>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>Key.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>- 命令行安装GAC
powershell
gacutil /i Demo.dllcsharp
// 代码方式安装GAC
using System.EnterpriseServices.Internal;
public static void InstallGac(string path)
{
new Publish().GacInstall(path);
}备注:
.NET Core/.NET5+不再支持GAC,改用NuGet共享
5. 值类型和引用类型的区别
解答
| 维度 | 值类型 | 引用类型 |
|---|---|---|
| 存储位置 | 栈Stack | 堆Heap |
| 赋值逻辑 | 拷贝完整值 | 拷贝内存地址 |
| 回收方式 | 栈自动释放 | GC垃圾回收 |
| 默认值 | 零值(0/false) | null |
| 基类 | ValueType | object |
csharp
// 值类型示例
int a = 10;
int b = a; b=20; // a不变
// 引用类型示例
int[] arr1 = new int[]{1};
int[] arr2 = arr1; arr2[0]=99; // arr1同步修改6. C#中string 和String 有什么区别
解答
- 本质完全一致:编译后均为
System.String string:C# 内置关键字别名,日常开发推荐String:.NET 框架完整类名,适合调用静态方法IL代码完全一致,无性能差异
csharp
string str1 = "test";
String str2 = "demo";
// 二者类型完全等同
Console.WriteLine(str1.GetType() == typeof(String));7. .NET中堆栈和堆的特点和差异
解答
栈 Stack
- 线程私有,内存连续,分配速度快
- 存储:值类型、对象引用地址
- 方法执行结束自动释放,无
GC
堆 Heap
- 进程共享,内存碎片化,分配较慢
- 存储:引用类型实例、数组
- 依赖GC标记-清除-压缩机制回收
csharp
public void MemTest()
{
int num = 10; // 栈内存
string s = new string("123"); // 引用在栈,实例在堆
}8. .NET中 GC 的运行机制
解答
GC(垃圾回收器):CLR 自动内存管理核心
- 三大阶段
- 标记:遍历根对象,标记存活对象
- 清除:回收未标记垃圾内存
- 压缩:整理内存,减少碎片
- 分代回收
- 0代:新建短期对象,回收频率最高
- 1代:缓冲过渡区域
- 2代:长期常驻对象,回收频率极低
- 触发时机:内存不足、手动调用、程序退出
csharp
// 手动触发GC(不推荐生产使用)
GC.Collect();
GC.WaitForPendingFinalizers();9. 简述 C#中重写、重载和隐藏的概念
解答
- 重载 Overload 同类别名方法,参数个数/类型不同,编译时多态
- 重写 Override 子类重写
virtual虚方法,运行时多态,完全覆盖逻辑 - 隐藏 New 子类隐藏父类同名方法,无多态,按引用类型调用
csharp
public class Base
{
public virtual void Show(){}
public void Print(){}
}
public class Son : Base
{
public override void Show(){} // 重写
public new void Print(){} // 隐藏
public void Add(int a){}
public void Add(int a,int b){} // 重载
}10. C#如何声明类不能被继承
解答
使用 sealed 密封关键字
- 密封类:禁止被继承
- 密封方法:禁止子类重写
csharp
// 密封类,无法继承
public sealed class FinalClass{}
// 密封重写方法
public class A
{
public virtual void Test(){}
}
public class B:A
{
public sealed override void Test(){}
}11. Int[]是引用类型还是值类型
解答
int[] 属于引用类型
- 所有数组都继承自
System.Array,属于引用类型 - 数组引用存栈,数组实例数据存堆
- 赋值时仅拷贝引用地址,共用同一组数据
csharp
// 验证数组类型
Console.WriteLine(typeof(int[]).IsClass); // True12. 解释泛型的基本原理
解答
泛型 Generics:通过类型参数实现代码复用 核心优势:类型安全、避免装箱拆箱、高性能 核心原理:编译时保留类型约束,运行时针对值类型/引用类型差异化实例化
csharp
// 泛型类
public class DataList<T>
{
public T Data { get; set; }
}
// 泛型约束
public static T Max<T>(T a,T b) where T:IComparable<T>13. Serializable 特性有何作用
解答
[Serializable]:标记类支持二进制序列化
- 序列化:对象转字节流,用于存储/网络传输
- 反序列化:字节流还原对象
- 搭配
[NonSerialized]可忽略不需要序列化的字段
csharp
[Serializable]
public class User
{
public string Name { get; set; }
[NonSerialized]
public string TempData; // 不参与序列化
}14. 如何自定义序列化和反序列化
解答
两种主流实现方式:
- 实现
ISerializable接口,完全控制序列化逻辑 - 使用序列化回调特性:
[OnSerializing]、OnDeserialized
csharp
[Serializable]
public class CustomData : ISerializable
{
public string Content { get; set; }
// 自定义序列化
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Data",Content);
}
// 自定义反序列化
protected CustomData(SerializationInfo info, StreamingContext context)
{
Content = info.GetString("Data");
}
}15. 如何使用 IFormattable 接口实现格式化输出
解答
实现 IFormattable 接口,重写ToString(string, IFormatProvider) 自定义格式符,实现多格式文本输出
csharp
public class Price : IFormattable
{
public decimal Value { get; set; }
public string ToString(string format, IFormatProvider formatProvider)
{
return format switch
{
"C"=>Value.ToString("C"),
"N"=>Value.ToString("N2"),
_=>Value.ToString()
};
}
}16. .NET提供了哪几个定时器类型
解答
| 定时器 | 适用场景 | 线程 |
|---|---|---|
Forms.Timer | WinForm UI | UI线程 |
DispatcherTimer | WPF | UI线程 |
Timers.Timer | 服务端、后台任务 | 工作线程 |
Threading.Timer | 轻量底层定时 | 线程池 |
csharp
// 常用后台定时器
var timer = new System.Timers.Timer(1000);
timer.Elapsed+=(s,e)=>Console.WriteLine("定时执行");
timer.Start();17. System.Object 三个比较方法异同
解答
- ReferenceEquals:强制比较内存引用,不可重写
- Equals(object):默认引用比较,可重写为值比较
- 静态Equals:空值安全处理,内部调用实例Equals
csharp
string a = "123";
string b = "123";
Console.WriteLine(ReferenceEquals(a,b));// 驻留池为True
Console.WriteLine(a.Equals(b));// 值相等True18. 请解释委托的基本原理
解答
Delegate委托:类型安全的方法指针
- 本质:继承
MulticastDelegate的特殊类 - 核心要素:方法地址、目标实例
- 作用:回调方法、解耦代码、实现事件、链式调用
csharp
// 委托声明与使用
public delegate void MsgDelegate(string msg);
MsgDelegate del = Console.WriteLine;
del("委托调用");19. 委托回调静态方法和实例方法有何区别
解答
- 静态委托:
Target=null,仅绑定方法,无对象依赖 - 实例委托:绑定具体对象,
Target指向实例,可访问成员 - 生命周期:实例委托随对象回收,静态委托常驻内存
csharp
public class Demo
{
public static void StaticFun(){}
public void InstanceFun(){}
}
// 静态委托
Action d1 = Demo.StaticFun;
// 实例委托
Action d2 = new Demo().InstanceFun;20. 什么是链式委托
解答
链式委托(多播委托):一个委托绑定多个方法
- 通过
+=追加方法,-=移除方法 - 调用时按添加顺序依次执行
- 返回值仅保留最后一个方法结果
- 单个方法异常会中断整个委托链
csharp
Action chain = Test1;
chain += Test2; // 链式绑定
chain(); // 依次执行两个方法21. 请解释事件的基本使用方法
解答
Event事件:委托的封装版本,发布订阅模式
- 限制外部仅能
+=/-=,防止委托覆盖 - 仅类内部可主动触发,安全性更高
- 标准结构:事件声明、触发方法、订阅绑定
csharp
public class EventDemo
{
// 声明事件
public event Action OnRun;
// 触发事件
public void Trigger()=>OnRun?.Invoke();
}
// 订阅
var e = new EventDemo();
e.OnRun+=()=>Console.WriteLine("事件触发");22. 反射的基本原理和其实现的基石
解答
反射 Reflection:运行时动态获取程序集、类、方法、属性信息
- 实现基石:元数据(Metadata)
- 核心命名空间:
System.Reflection - 核心能力:动态创建对象、调用方法、修改属性、读取特性
csharp
// 反射基础示例
Type type = typeof(User);
var obj = Activator.CreateInstance(type);
var method = type.GetMethod("Show");
method.Invoke(obj,null);