C# 桌面端开发工程师面试题 + 参考答案

下面是一版 C# 桌面端开发工程师面试题 + 参考答案,按你列的方向整理,适合明天面试前背诵。

一、C# 基础

1. 值类型和引用类型有什么区别?

答:

值类型直接保存数据,常见有 intdoubleboolstructenum

引用类型保存对象的引用,对象本身在托管堆上,常见有 classstring、数组、委托、接口。

区别:

  • 值类型赋值会复制数据本身

  • 引用类型赋值会复制引用地址

  • 值类型默认不能为 null

  • 引用类型默认值是 null

  • 值类型通常适合小数据结构,引用类型适合复杂对象

示例:

复制代码
int a = 10;
int b = a;
b = 20;
// a 还是 10

Person p1 = new Person();
Person p2 = p1;
// p1 和 p2 指向同一个对象

2. 什么是装箱和拆箱?

答:

装箱是值类型转换成引用类型。

复制代码
int a = 10;
object obj = a;

拆箱是引用类型转换回值类型。

复制代码
int b = (int)obj;

装箱和拆箱会产生性能开销,频繁操作时应避免,比如优先使用泛型集合 List<int>,不要使用 ArrayList


3. classstruct 有什么区别?

答:

class 是引用类型,struct 是值类型。

区别:

  • class 对象通常在堆上分配

  • struct 通常用于小型数据结构

  • class 支持继承,struct 不支持继承其他结构或类

  • struct 不能显式声明无参构造函数

  • class 默认值是 nullstruct 默认值是字段默认值

比如坐标点、颜色、日期等适合用 struct;业务对象如用户、订单适合用 class


4. refoutin 有什么区别?

答:

它们都是按引用传递参数。

ref:传入前必须赋值,方法内可以修改。

复制代码
void Add(ref int x)
{
    x++;
}

out:传入前可以不赋值,但方法内部必须赋值。

复制代码
void GetValue(out int x)
{
    x = 100;
}

in:按引用传递,但方法内部不能修改,适合传递较大的结构体,减少复制成本。

复制代码
void Print(in int x)
{
    Console.WriteLine(x);
}

5. 委托是什么?

答:

委托是类型安全的函数指针,可以把方法当成参数传递。

复制代码
delegate void MyDelegate(string msg);

void Print(string msg)
{
    Console.WriteLine(msg);
}

MyDelegate d = Print;
d("Hello");

常见用途:

  • 回调函数

  • 事件机制

  • 异步回调

  • LINQ

  • UI 事件处理


6. 事件和委托有什么关系?

答:

事件是基于委托实现的。

委托可以在外部直接调用,而事件只能在声明它的类内部触发。

复制代码
public event EventHandler Click;

桌面开发中,按钮点击、窗口关闭、文本变化,本质上都是事件机制。


7. 泛型有什么作用?

答:

泛型可以让类型参数化,提高代码复用性、类型安全性和性能。

复制代码
List<int> list = new List<int>();
list.Add(1);

优点:

  • 避免强制类型转换

  • 避免装箱拆箱

  • 编译期检查类型

  • 提高代码复用性

常见泛型类型有:

复制代码
List<T>
Dictionary<TKey, TValue>
Nullable<T>
Task<T>

8. 异常处理机制是什么?

答:

C# 使用 try-catch-finally 处理异常。

复制代码
try
{
    // 可能出错的代码
}
catch (Exception ex)
{
    // 记录日志或提示用户
}
finally
{
    // 释放资源
}

执行逻辑:

  • 没异常:执行 try,再执行 finally

  • 有异常并捕获:执行 catch,再执行 finally

  • 有异常未捕获:执行 finally,然后继续向上抛出


9. throwthrow ex 有什么区别?

答:

throw 会保留原始异常堆栈。

复制代码
catch (Exception)
{
    throw;
}

throw ex 会重置堆栈信息,不利于定位真实错误位置。

复制代码
catch (Exception ex)
{
    throw ex;
}

实际开发中推荐使用 throw


二、桌面开发 WinForms / WPF

10. WinForms 和 WPF 有什么区别?

答:

WinForms 是传统桌面 UI 框架,基于控件和事件,开发简单,适合企业管理系统。

WPF 是基于 XAML 的 UI 框架,支持数据绑定、样式、模板、动画和 MVVM。

区别:

  • WinForms 上手快,适合简单界面

  • WPF 表现力更强,适合复杂 UI

  • WPF 支持数据绑定和 MVVM

  • WPF 更容易做界面和逻辑分离


11. 为什么不能在子线程直接更新 UI?

答:

WinForms 和 WPF 的 UI 控件只能由创建它们的 UI 线程访问。

如果子线程直接修改 UI,可能出现跨线程异常。

WinForms:

复制代码
this.Invoke(new Action(() =>
{
    label1.Text = "完成";
}));

WPF:

复制代码
Dispatcher.Invoke(() =>
{
    textBlock.Text = "完成";
});

12. 什么是 UI 线程?

答:

UI 线程是负责创建和刷新界面的线程。

桌面程序中,窗口、按钮、文本框等控件都运行在 UI 线程上。

如果在 UI 线程执行耗时操作,比如数据库查询、文件读写、网络请求,就会导致界面卡死。

解决方式是把耗时操作放到后台线程,完成后再切回 UI 线程更新界面。


13. WPF 数据绑定是什么?

答:

数据绑定是 UI 和数据对象之间的自动同步机制。

复制代码
<TextBox Text="{Binding UserName}" />

ViewModel:

复制代码
public string UserName { get; set; }

常见绑定模式:

  • OneWay:数据源到界面

  • TwoWay:界面和数据源双向同步

  • OneTime:只绑定一次

  • OneWayToSource:界面到数据源


14. 什么是 MVVM?

答:

MVVM 是 WPF 常用架构模式。

  • Model:数据模型

  • View:界面

  • ViewModel:界面逻辑和绑定数据

优点:

  • UI 和业务逻辑解耦

  • 方便维护

  • 方便单元测试

  • 减少后台代码

  • 适合复杂桌面项目


15. WPF 中 Command 的作用是什么?

答:

Command 用于把按钮点击等操作绑定到 ViewModel,替代传统事件。

复制代码
<Button Content="保存" Command="{Binding SaveCommand}" />

好处:

  • 符合 MVVM

  • 减少 View 后台代码

  • 可以控制按钮是否可用

  • 逻辑更容易测试


16. 什么是依赖属性 DependencyProperty?

答:

依赖属性是 WPF 的属性系统,用于支持数据绑定、样式、动画、属性值继承等功能。

普通属性:

复制代码
public string Name { get; set; }

依赖属性:

复制代码
public static readonly DependencyProperty TitleProperty =
    DependencyProperty.Register(
        "Title",
        typeof(string),
        typeof(MyControl),
        new PropertyMetadata("")
    );

public string Title
{
    get => (string)GetValue(TitleProperty);
    set => SetValue(TitleProperty, value);
}

常用于自定义控件,让属性支持绑定和样式设置。


17. INotifyPropertyChanged 接口有什么作用?

答:

INotifyPropertyChanged 用来通知 UI:某个属性发生变化了。

如果 ViewModel 的属性变化后希望界面自动刷新,就需要实现它。

复制代码
public class MainViewModel : INotifyPropertyChanged
{
    private string _name;

    public string Name
    {
        get => _name;
        set
        {
            _name = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

面试可以这样说:

WPF 数据绑定时,如果后台属性变化后要通知界面刷新,通常需要实现 INotifyPropertyChanged


三、异步、多线程、Task

18. 进程和线程有什么区别?

答:

进程是程序运行的基本单位,有独立的内存空间。

线程是进程中的执行单元,一个进程可以有多个线程。

区别:

  • 进程之间相互独立

  • 同一进程内的线程共享内存

  • 线程切换成本比进程低

  • 多线程共享数据时要注意线程安全


19. ThreadTask 有什么区别?

答:

Thread 是底层线程,需要手动创建和管理。

复制代码
Thread t = new Thread(() =>
{
    Console.WriteLine("运行");
});
t.Start();

Task 是更高级的任务抽象,通常使用线程池,适合异步和并发开发。

复制代码
await Task.Run(() =>
{
    Console.WriteLine("运行");
});

实际项目中更推荐使用 Taskasyncawait


20. asyncawait 是什么?

答:

async 表示一个方法是异步方法。

await 表示等待一个异步任务完成,但不会阻塞当前线程。

复制代码
private async void Button_Click(object sender, EventArgs e)
{
    string data = await GetDataAsync();
    textBox.Text = data;
}

桌面程序中使用 async/await 可以避免 UI 卡死。


21. Task.Run 的作用是什么?

答:

Task.Run 用于把 CPU 密集型或耗时同步操作放到线程池中执行。

复制代码
await Task.Run(() =>
{
    // 耗时计算
});

常见场景:

  • 大量数据计算

  • 文件处理

  • 图片处理

  • 不支持异步 API 的耗时操作

但如果是数据库、网络请求,优先使用真正的异步方法,比如 ExecuteReaderAsync()HttpClient.GetAsync()


22. 多线程会带来什么问题?

答:

多线程常见问题有:

  • 线程安全问题

  • 死锁

  • 资源竞争

  • UI 跨线程访问异常

  • 调试困难

  • 任务取消和异常处理复杂

解决方式:

  • 使用 lock

  • 使用线程安全集合

  • 使用 SemaphoreSlim

  • 使用 CancellationToken

  • UI 更新切回主线程

  • 尽量使用 Taskasync/await


23. 什么是线程安全?

答:

多个线程同时访问同一份数据时,程序结果仍然正确,就叫线程安全。

非线程安全示例:

复制代码
count++;

count++ 不是原子操作,多个线程同时执行可能导致结果错误。

可以使用 lock

复制代码
private readonly object _lock = new object();

lock (_lock)
{
    count++;
}

也可以使用原子操作:

复制代码
Interlocked.Increment(ref count);

24. 什么是死锁?

答:

死锁是多个线程互相等待对方释放资源,导致程序一直卡住。

常见原因:

  • 多个锁顺序不一致

  • 在 UI 线程同步等待异步任务

  • 锁内执行耗时操作

典型错误:

复制代码
var result = GetDataAsync().Result;

在 UI 程序中可能导致死锁。

推荐:

复制代码
var result = await GetDataAsync();

25. 多线程之间如何通信?

答:

常见方式:

  • 共享变量 + lock

  • Monitor.Wait / Monitor.Pulse

  • AutoResetEvent

  • ManualResetEvent

  • SemaphoreSlim

  • BlockingCollection

  • ConcurrentQueue

  • 事件回调

  • SynchronizationContext

  • UI 框架中的 DispatcherInvoke

桌面开发中最常见的是:

后台线程处理数据,完成后通过 Dispatcher.InvokeControl.Invoke 通知 UI 线程更新界面。


26. 什么是协程?

答:

C# 标准语境里不常直接说"协程",更多使用 async/await、迭代器或状态机。

如果是 Unity 开发,协程通常指 IEnumerator

复制代码
IEnumerator LoadData()
{
    yield return new WaitForSeconds(1);
    Debug.Log("完成");
}

在普通 C#/.NET 桌面开发中,async/await 可以理解为一种类似协程的异步编程模型,它可以在等待耗时操作时让出执行权,完成后再继续执行后面的代码。


四、进程间通信 IPC

27. 什么是进程间通信?

答:

进程间通信就是不同进程之间交换数据。

因为不同进程内存空间相互独立,不能像线程一样直接共享变量,所以需要 IPC 机制。

常见方式:

  • 命名管道 NamedPipe

  • Socket

  • HTTP API

  • gRPC

  • 消息队列

  • 共享内存

  • 文件

  • Windows 消息

  • COM


28. 桌面程序常用哪些进程间通信方式?

答:

常见选择:

  • 本机两个程序通信:命名管道

  • 跨机器通信:Socket、HTTP、gRPC

  • 简单数据交换:文件、数据库

  • Windows 桌面程序集成:Windows 消息、COM

面试可以说:

如果是同一台机器上的两个 C# 程序通信,我会优先考虑命名管道;如果需要跨机器通信,我会考虑 Socket、HTTP 或 gRPC。


五、数据库

29. ADO.NET 操作数据库的流程是什么?

答:

基本流程:

  1. 创建连接 SqlConnection

  2. 创建命令 SqlCommand

  3. 打开连接

  4. 执行 SQL

  5. 读取结果

  6. 关闭连接

示例:

复制代码
using SqlConnection conn = new SqlConnection(connStr);
using SqlCommand cmd = new SqlCommand("SELECT * FROM Users", conn);

conn.Open();

using SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
    Console.WriteLine(reader["Name"]);
}

推荐使用 using 自动释放连接资源。


30. 如何防止 SQL 注入?

答:

使用参数化查询,不要拼接 SQL。

错误写法:

复制代码
string sql = "SELECT * FROM Users WHERE Name = '" + name + "'";

正确写法:

复制代码
string sql = "SELECT * FROM Users WHERE Name = @Name";
cmd.Parameters.AddWithValue("@Name", name);

参数化查询可以避免用户输入被当成 SQL 语句执行。


31. 什么是事务?

答:

事务用于保证一组数据库操作要么全部成功,要么全部失败。

事务有 ACID 特性:

  • 原子性:全部成功或全部失败

  • 一致性:数据从一个正确状态变成另一个正确状态

  • 隔离性:事务之间互不干扰

  • 持久性:提交后数据永久保存

示例:

复制代码
SqlTransaction tran = conn.BeginTransaction();

try
{
    cmd.Transaction = tran;
    cmd.ExecuteNonQuery();

    tran.Commit();
}
catch
{
    tran.Rollback();
    throw;
}

常见场景:转账、订单创建、库存扣减。


32. ExecuteNonQueryExecuteScalarExecuteReader 区别?

答:

ExecuteNonQuery:执行增删改,返回受影响行数。

复制代码
cmd.ExecuteNonQuery();

ExecuteScalar:返回单个值,比如数量、ID。

复制代码
int count = (int)cmd.ExecuteScalar();

ExecuteReader:读取多行多列数据。

复制代码
SqlDataReader reader = cmd.ExecuteReader();

六、数据结构

33. 数组有什么特点?

答:

数组是一块连续内存,长度固定。

优点:

  • 按下标访问快,时间复杂度 O(1)

  • 结构简单

缺点:

  • 长度固定

  • 中间插入和删除成本高,需要移动元素

适合场景:

  • 数据数量固定

  • 高频按下标访问


34. 哈希表有什么特点?

答:

哈希表通过哈希函数把 Key 映射到存储位置。

C# 中常用:

复制代码
Dictionary<TKey, TValue>
Hashtable

特点:

  • 查询快,平均 O(1)

  • Key 唯一

  • 不保证顺序

  • 哈希冲突时需要处理冲突

实际开发中常用 Dictionary<TKey, TValue> 做缓存、映射、快速查找。


35. 链表有什么特点?

答:

链表由一个个节点组成,每个节点保存数据和指向下一个节点的引用。

特点:

  • 插入、删除节点方便

  • 不需要连续内存

  • 按下标访问慢,需要从头遍历

  • 查询复杂度通常是 O(n)

C# 中有:

复制代码
LinkedList<T>

数组适合查询多,链表适合插入删除多。


七、项目经验题

36. 桌面程序界面卡死怎么排查?

答:

界面卡死通常是 UI 线程执行了耗时操作。

常见原因:

  • 数据库查询太慢

  • 文件读写耗时

  • 网络请求阻塞

  • 一次性加载大量数据

  • 循环计算放在 UI 线程

解决方式:

  • 使用 async/await

  • 耗时任务放到后台线程

  • 分页加载数据

  • 表格使用虚拟化

  • 减少 UI 频繁刷新

  • 增加日志定位耗时点


37. 如何做异常日志?

答:

一般会捕获异常并记录:

  • 异常时间

  • 异常类型

  • 异常消息

  • 堆栈信息

  • 当前用户

  • 操作步骤

  • 输入参数

可以使用:

  • log4net

  • NLog

  • Serilog

  • 自定义文件日志

示例:

复制代码
try
{
    SaveData();
}
catch (Exception ex)
{
    logger.Error(ex, "保存数据失败");
    MessageBox.Show("保存失败,请联系管理员");
}

用户看到友好提示,开发人员通过日志定位问题。


38. 如何优化桌面程序性能?

答:

可以从几个方向优化:

  • UI 线程不执行耗时操作

  • 大数据使用分页加载

  • 表格开启虚拟化

  • 数据库 SQL 加索引

  • 减少重复查询,适当使用缓存

  • 避免频繁创建大对象

  • 及时释放资源

  • 减少不必要的 UI 刷新

  • 使用异步加载

  • 通过日志或性能分析工具定位瓶颈


39. 表格数据很多时怎么展示?

答:

不能一次性全部加载到界面。

常见优化方式:

  • 分页查询

  • 滚动加载

  • 虚拟化显示

  • 后台异步加载

  • 只加载必要字段

  • 数据库层面加索引

  • 搜索条件限制数据范围

WPF 中可以开启虚拟化:

复制代码
<DataGrid EnableRowVirtualization="True"
          EnableColumnVirtualization="True" />

40. 如果上线后程序报错,你怎么处理?

答:

我会这样排查:

  1. 查看日志,确认异常信息和堆栈

  2. 了解用户操作步骤

  3. 检查输入数据、配置、数据库和网络环境

  4. 本地尝试复现

  5. 定位代码问题并修复

  6. 增加异常保护和日志,避免再次难以排查

面试可以补一句:

我不会只看用户描述,会优先看日志和堆栈,因为这样定位更准确。


八、综合高频题

41. 如何避免资源泄露?

答:

对实现了 IDisposable 的对象使用 using

复制代码
using FileStream fs = new FileStream(path, FileMode.Open);

常见需要释放的资源:

  • 数据库连接

  • 文件流

  • 网络连接

  • 图片对象

  • Socket

  • 定时器


42. List<T> 和数组有什么区别?

答:

数组长度固定,访问速度快。

List<T> 底层也是数组,但可以动态扩容,使用更方便。

区别:

  • 数组长度固定

  • List<T> 长度可变

  • List<T> 支持 AddRemoveFind

  • 高频固定长度数据可以用数组

  • 业务集合通常用 List<T>


43. DictionaryList 查询有什么区别?

答:

List 查找通常需要遍历,复杂度是 O(n)

Dictionary 通过 Key 查找,平均复杂度是 O(1)

如果需要通过编号、名称、ID 快速查找对象,适合用 Dictionary


44. 面试中怎么介绍自己的 C# 桌面项目?

答:

可以这样说:

我做过一个 C# 桌面端管理系统,使用 WinForms/WPF 开发界面,数据库使用 SQL Server/MySQL。项目中我主要负责界面功能、数据增删改查、表格展示、用户输入校验、异常日志和部分性能优化。

项目里遇到过界面卡顿问题,后来通过 async/await、后台任务、分页查询和减少 UI 刷新来优化。对于异常问题,我会记录日志,包括异常信息、堆栈和用户操作,方便后续排查。


明天面试时你可以重点背这几个关键词:

值类型/引用类型委托/事件泛型try-catch-finallyWinForms/WPFUI线程MVVM数据绑定INotifyPropertyChanged依赖属性Taskasync/await线程安全lock多线程通信ADO.NET参数化查询事务分页加载异常日志

下面是一套适合 C# 桌面端开发工程师 面试前突击复习的题目和参考答案,重点覆盖:C# 基础、.NET、WinForms/WPF、线程、异步、数据库、MVVM、性能与项目经验。

一、C# 基础

1. C# 中值类型和引用类型有什么区别?

答案:

值类型直接存储数据,通常分配在栈上,例如:

复制代码
int a = 10;
struct Point { public int X; public int Y; }

引用类型存储的是对象的引用,对象本身通常分配在托管堆上,例如:

复制代码
class Person { public string Name; }

主要区别:

  • 值类型赋值时复制数据本身

  • 引用类型赋值时复制引用地址

  • 值类型默认不能为 null,除非使用 Nullable<T>

  • 常见值类型:intdoubleboolstructenum

  • 常见引用类型:classstringarraydelegateinterface


2. refout 有什么区别?

答案:

refout 都是按引用传递参数。

区别:

  • ref 参数在传入前必须初始化

  • out 参数在方法内部必须赋值

  • ref 更适合修改已有变量

  • out 更适合返回多个结果

示例:

复制代码
void Add(ref int x)
{
    x += 10;
}

void GetResult(out int result)
{
    result = 100;
}

3. constreadonly 有什么区别?

答案:

const 是编译期常量,声明时必须赋值,之后不能修改。

复制代码
public const double PI = 3.14;

readonly 是运行时常量,可以在声明时或构造函数中赋值。

复制代码
public readonly string Name;

public Person(string name)
{
    Name = name;
}

区别:

  • const 是静态的,属于类型

  • readonly 可以是实例字段

  • const 只能用于基础常量类型

  • readonly 可以用于对象引用


4. stringStringBuilder 有什么区别?

答案:

string 是不可变对象,每次拼接都会产生新的字符串对象。

复制代码
string s = "";
s += "A";
s += "B";

大量拼接时性能较差。

StringBuilder 是可变字符串对象,适合频繁拼接。

复制代码
StringBuilder sb = new StringBuilder();
sb.Append("A");
sb.Append("B");

如果只是少量字符串拼接,使用 string 即可;如果在循环中大量拼接,推荐使用 StringBuilder


二、面向对象

5. C# 中接口和抽象类有什么区别?

答案:

接口定义能力,抽象类定义公共基础。

接口:

复制代码
interface ILogger
{
    void Log(string message);
}

抽象类:

复制代码
abstract class Animal
{
    public abstract void Speak();

    public void Eat()
    {
        Console.WriteLine("Eat");
    }
}

区别:

  • 一个类可以实现多个接口,但只能继承一个类

  • 接口更强调行为规范

  • 抽象类可以包含字段、构造函数、普通方法

  • 抽象类适合有公共代码复用的场景

  • 接口适合解耦和扩展


6. 什么是多态?

答案:

多态是指同一个方法调用,在不同对象上表现出不同的行为。

常见实现方式:

  • 方法重写 override

  • 接口实现

  • 虚方法 virtual

示例:

复制代码
class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("Animal");
    }
}

class Dog : Animal
{
    public override void Speak()
    {
        Console.WriteLine("Dog");
    }
}

调用:

复制代码
Animal animal = new Dog();
animal.Speak(); // 输出 Dog

三、集合与泛型

7. ArrayListList<T> 有什么区别?

答案:

ArrayList 是非泛型集合,存储的是 object,使用时可能发生装箱、拆箱,类型不安全。

复制代码
ArrayList list = new ArrayList();
list.Add(1);
list.Add("abc");

List<T> 是泛型集合,类型安全,性能更好。

复制代码
List<int> list = new List<int>();
list.Add(1);

实际项目中一般使用 List<T>


8. Dictionary<TKey, TValue> 的底层原理是什么?

答案:

Dictionary 基于哈希表实现,通过 Key 的哈希值快速定位数据。

特点:

  • 查询速度快,平均时间复杂度接近 O(1)

  • Key 必须唯一

  • Key 类型应正确实现 GetHashCode()Equals()

  • 不保证顺序

常用于缓存、映射关系、快速查找。


四、异常处理

9. try-catch-finally 的执行顺序是什么?

答案:

正常情况下:

复制代码
try
{
    // 执行业务代码
}
catch
{
    // 捕获异常
}
finally
{
    // 无论是否异常都会执行
}

执行顺序:

  • 没有异常:tryfinally

  • 有异常且被捕获:trycatchfinally

  • 有异常但未捕获:tryfinally → 向上抛出异常

finally 常用于释放资源,例如关闭文件、数据库连接。


五、委托、事件、Lambda

10. 什么是委托?

答案:

委托可以理解为类型安全的函数指针,用于把方法作为参数传递。

复制代码
delegate void MyDelegate(string message);

void Print(string msg)
{
    Console.WriteLine(msg);
}

MyDelegate del = Print;
del("Hello");

常见用途:

  • 回调函数

  • 事件机制

  • LINQ

  • 异步编程


11. 事件和委托有什么关系?

答案:

事件是基于委托实现的。

委托可以在外部直接调用,而事件只能在声明它的类内部触发。

复制代码
public event EventHandler Click;

事件常用于桌面开发中的按钮点击、窗口关闭、数据变化通知等场景。


六、线程与异步

12. 线程和进程有什么区别?

答案:

进程是程序运行的基本单位,拥有独立的内存空间。

线程是进程中的执行单元,多个线程共享同一个进程的资源。

区别:

  • 一个进程可以有多个线程

  • 进程之间相互独立

  • 线程之间共享内存

  • 线程切换成本比进程低

  • 多线程需要注意线程安全问题


13. TaskThread 有什么区别?

答案:

Thread 是比较底层的线程创建方式。

复制代码
Thread thread = new Thread(() =>
{
    Console.WriteLine("Run");
});
thread.Start();

Task 是更高级的任务抽象,基于线程池,更适合异步和并发操作。

复制代码
await Task.Run(() =>
{
    Console.WriteLine("Run");
});

实际项目中更推荐使用 Taskasync/await


14. asyncawait 的作用是什么?

答案:

async/await 用于简化异步编程,让异步代码写起来像同步代码。

复制代码
private async void Button_Click(object sender, EventArgs e)
{
    string result = await GetDataAsync();
    MessageBox.Show(result);
}

优点:

  • 避免阻塞 UI 线程

  • 提高程序响应性

  • 代码可读性更好

桌面程序中,耗时操作如网络请求、数据库查询、文件读写,应该使用异步方式,避免界面卡死。


15. 为什么不能在子线程直接更新 UI?

答案:

WinForms 和 WPF 的 UI 控件都只能由创建它们的 UI 线程访问。

如果在子线程直接更新 UI,可能会抛出跨线程访问异常。

WinForms 中可以使用:

复制代码
this.Invoke(new Action(() =>
{
    label1.Text = "完成";
}));

WPF 中可以使用:

复制代码
Dispatcher.Invoke(() =>
{
    TextBlock.Text = "完成";
});

七、WinForms / WPF 桌面开发

16. WinForms 和 WPF 有什么区别?

答案:

WinForms 是较早的桌面 UI 框架,基于控件和事件驱动,开发简单,适合传统企业管理系统。

WPF 是基于 XAML 的 UI 框架,支持数据绑定、样式、模板、动画和 MVVM 架构。

区别:

  • WinForms 上手简单,适合快速开发

  • WPF UI 表现力更强

  • WPF 支持数据绑定和 MVVM

  • WPF 更适合复杂界面和可维护性要求高的项目


17. WPF 中什么是数据绑定?

答案:

数据绑定是 UI 和数据源之间的自动同步机制。

示例:

复制代码
<TextBox Text="{Binding UserName}" />

ViewModel:

复制代码
public string UserName { get; set; }

当数据源变化时,UI 可以自动更新;当用户修改 UI 时,也可以同步到数据源。

常见绑定模式:

  • OneWay

  • TwoWay

  • OneTime

  • OneWayToSource


18. 什么是 MVVM?

答案:

MVVM 是 WPF 常用架构模式,分为:

  • Model:数据模型

  • View:界面

  • ViewModel:界面逻辑和数据绑定

优点:

  • UI 和业务逻辑解耦

  • 方便单元测试

  • 代码更清晰

  • 更适合复杂桌面项目

View 通过 Binding 绑定 ViewModel,按钮事件通常通过 Command 处理。


19. WPF 中 INotifyPropertyChanged 有什么作用?

答案:

INotifyPropertyChanged 用于通知 UI 属性发生变化。

示例:

复制代码
public class MainViewModel : INotifyPropertyChanged
{
    private string _name;

    public string Name
    {
        get => _name;
        set
        {
            _name = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

如果没有实现它,后台属性变化后,界面可能不会自动刷新。


20. WPF 中 Command 有什么作用?

答案:

Command 用于替代传统事件处理,把按钮点击等操作绑定到 ViewModel。

复制代码
<Button Content="保存" Command="{Binding SaveCommand}" />

优点:

  • 减少后台代码

  • 方便 MVVM 解耦

  • 可以控制按钮是否可用

  • 便于测试业务逻辑


八、数据库与文件操作

21. ADO.NET 操作数据库的一般流程是什么?

答案:

一般流程:

  1. 创建数据库连接 SqlConnection

  2. 创建命令对象 SqlCommand

  3. 打开连接

  4. 执行 SQL

  5. 读取结果

  6. 关闭连接

示例:

复制代码
using SqlConnection conn = new SqlConnection(connStr);
using SqlCommand cmd = new SqlCommand("SELECT * FROM Users", conn);

conn.Open();

using SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
    Console.WriteLine(reader["Name"]);
}

推荐使用 using 自动释放数据库连接。


22. 如何防止 SQL 注入?

答案:

不要直接拼接 SQL 字符串,应使用参数化查询。

错误写法:

复制代码
string sql = "SELECT * FROM Users WHERE Name = '" + name + "'";

正确写法:

复制代码
string sql = "SELECT * FROM Users WHERE Name = @Name";
cmd.Parameters.AddWithValue("@Name", name);

参数化查询可以避免用户输入被当成 SQL 语句执行。


九、常见项目问题

23. 桌面程序界面卡死一般是什么原因?

答案:

通常是因为在 UI 线程执行了耗时操作,例如:

  • 大量数据处理

  • 数据库查询

  • 网络请求

  • 文件读写

  • 循环计算

解决方式:

  • 使用 Task.Run

  • 使用 async/await

  • 分页加载数据

  • 后台线程处理,UI 线程只负责界面更新


24. 你如何优化一个 C# 桌面程序?

答案:

可以从几个方面优化:

  • 避免 UI 线程执行耗时任务

  • 大量数据使用分页或虚拟化

  • 减少不必要的控件刷新

  • 使用异步加载数据

  • 及时释放数据库连接、文件流等资源

  • 避免频繁创建大对象

  • 使用缓存减少重复查询

  • 使用日志定位性能瓶颈


25. 如果程序上线后出现异常,你怎么排查?

答案:

我会按以下步骤排查:

  1. 查看日志,确认异常类型和堆栈信息

  2. 确认用户操作路径和输入数据

  3. 本地尝试复现问题

  4. 检查数据库、配置文件、网络环境

  5. 定位代码问题并修复

  6. 增加异常处理和日志,避免同类问题再次难以排查


十、面试高频简答

26. 什么是垃圾回收 GC?

答案:

GC 是 .NET 的自动内存管理机制,用于回收不再被引用的托管对象。

它可以减少手动释放内存的复杂度,但并不代表所有资源都不用释放。

例如数据库连接、文件流、Socket 等非托管资源,仍然应该使用 Dispose()using 及时释放。


27. IDisposable 有什么作用?

答案:

IDisposable 用于释放资源,尤其是非托管资源。

常见对象:

  • FileStream

  • SqlConnection

  • SqlDataReader

  • Bitmap

  • Socket

推荐写法:

复制代码
using FileStream fs = new FileStream("a.txt", FileMode.Open);

using 结束后会自动调用 Dispose()


28. ==Equals() 有什么区别?

答案:

== 是运算符,可以被重载。

Equals() 是对象方法,用于判断两个对象是否相等。

对于引用类型,默认情况下通常比较引用地址。

对于 string==Equals() 通常比较字符串内容,因为 string 重载了 ==


29. 什么是装箱和拆箱?

答案:

装箱是值类型转换为引用类型。

复制代码
int a = 10;
object obj = a;

拆箱是引用类型转换回值类型。

复制代码
int b = (int)obj;

装箱和拆箱会带来性能开销,频繁操作时应尽量避免。


30. 你做过的桌面项目可以怎么介绍?

答案模板:

我之前做过一个基于 C# 的桌面端管理系统,主要使用 WinForms/WPF 开发界面,数据库使用 SQL Server/MySQL。

项目中我主要负责:

  • 界面功能开发

  • 数据增删改查

  • 表格数据展示和分页

  • 用户输入校验

  • 异常处理和日志记录

  • 部分耗时操作的异步处理

项目中遇到过界面卡顿问题,后来通过 async/await、后台任务和分页加载进行了优化,提升了用户操作体验。


面试回答技巧

你可以重点准备这几个方向:

  • C# 基础:值类型、引用类型、委托、事件、泛型、异常

  • 桌面开发:WinForms/WPF、UI 线程、数据绑定、MVVM

  • 异步线程:TaskThreadasync/await

  • 数据库:ADO.NET、参数化查询、事务

  • 项目经验:界面卡顿、异常日志、性能优化、数据展示

如果明天面的是初中级 C# 桌面端岗位,能把上面这些问题讲清楚,基本可以覆盖大部分面试内容。

相关推荐
梦幻通灵1 小时前
Java传递负数金额被默认转化为0处理方案
java·开发语言
七夜zippoe1 小时前
OpenClaw Canvas 执行:JavaScript 注入实战
开发语言·javascript·udp·canvas·openclaw
黄啊码1 小时前
【黄啊码】拉勾倒了,但你的简历早就不该在招聘软件上了
人工智能·面试
雨落在了我的手上1 小时前
初识java(十五):字符串-String类
java·开发语言
zzx2006__1 小时前
JavaScript最终考核
开发语言·前端·javascript
努力努力再努力wz1 小时前
【Qt入门系列】:QLabel控件详解:从文本显示到图片展示,再到内容布局与伙伴机制
android·开发语言·数据结构·数据库·c++·qt·mysql
Aphasia3112 小时前
Vite配置代理和后端服务器CORS
面试
甄心爱学习2 小时前
【项目实训(个人10)】
开发语言·前端·javascript
触底反弹2 小时前
dom操作这篇文章就够了
javascript·面试