C#(WPF)-八股文
第一部分:WPF 核心知识
1. 什么是 WPF?和 WinForms 有什么区别?
WPF 是基于 DirectX 的桌面 UI 框架,使用 XAML 构建界面。
| 对比项 | WPF | WinForms |
|---|---|---|
| 渲染引擎 | DirectX | GDI/GDI+ |
| 界面描述 | XAML + C# | 纯 C# / 设计器 |
| 数据绑定 | 强大 | 较弱 |
| 样式模板 | 支持 | 基本不支持 |
| 动画 | 支持 | 基本不支持 |
| 架构模式 | 适合 MVVM | 事件驱动 |
2. WPF 的核心特点
- XAML
- 数据绑定
- 依赖属性
- 路由事件
- 样式与模板
- 动画
- MVVM
3. 什么是 XAML?
- XAML 是一种声明式标记语言,用来描述 UI。
- 本质上会被编译成 BAML,再由 WPF 加载生成对象树。
- 优点是 界面和业务逻辑分离。
4. 什么是逻辑树(Logical Tree)和视觉树(Visual Tree)?
- 逻辑树:描述控件之间的逻辑包含关系,比如 Window -> Grid -> Button。
- 视觉树:描述控件最终渲染出来的完整结构,包含模板生成的内部元素。
- 查找控件、事件路由、样式应用时常会涉及这两个概念。
5. 什么是依赖属性(DependencyProperty)?
WPF 特有属性系统,不只是普通 CLR 属性。
支持:
- 数据绑定
- 样式
- 动画
- 默认值
- 属性值继承
- 变更通知
常用于自定义控件。
6. 依赖属性和普通属性有什么区别?
- 普通属性:只是字段封装。
- 依赖属性:由 WPF 属性系统管理,可参与绑定、动画、样式等。
- 一般控件公开可绑定的属性,优先考虑依赖属性。
7. 如何注册一个依赖属性?
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);
}8. 什么是附加属性(Attached Property)?
- 某个类定义的属性,可以"附加"到别的对象上使用。
- 典型例子:
Grid.Row、Canvas.Left - 本质上也是依赖属性。
9. 什么是路由事件(RoutedEvent)?
事件可以在元素树中传播,不只是当前控件自己处理。
三种类型:
- 冒泡(Bubbling):从源控件向父级传播,如
Click - 隧道(Tunneling):从根向源控件传播,一般以
Preview开头,如PreviewMouseDown - 直接(Direct):只在当前控件触发
10. 什么是数据绑定(Binding)?
- 将 UI 和数据对象连接起来。
- UI 显示数据、用户修改数据时可同步到对象。
- 是 WPF 和 MVVM 的核心。
11. 常见 Binding Mode 有哪些?
| 模式 | 说明 |
|---|---|
OneWay |
数据源 -> UI |
TwoWay |
双向同步 |
OneTime |
只绑定一次 |
OneWayToSource |
UI -> 数据源 |
Default |
使用控件默认模式 |
常见追问:
TextBox.Text默认是 TwoWay- 但
UpdateSourceTrigger默认通常是 LostFocus
12. 什么是 DataContext?
- DataContext 是绑定时默认的数据源。
- 它会沿着视觉/逻辑树向下继承。
- 子元素没显式指定 Source 时,会自动使用 DataContext。
13. Binding 中 Source、ElementName、RelativeSource 的区别?
| 属性 | 说明 |
|---|---|
Source |
手动指定数据源对象 |
ElementName |
绑定到某个控件 |
RelativeSource |
绑定到相对位置对象,如自己、父元素、TemplatedParent |
14. UpdateSourceTrigger 有哪些?
| 值 | 说明 |
|---|---|
PropertyChanged |
属性变化立即更新 |
LostFocus |
失去焦点更新 |
Explicit |
手动更新 |
15. 什么是 INotifyPropertyChanged?
当对象属性变化时通知 UI 刷新。ViewModel 基本都会实现它。
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}16. ObservableCollection 和 List 的区别?
| 类型 | 特点 |
|---|---|
List<T> |
集合变化不会自动通知 UI |
ObservableCollection<T> |
增删集合项时会通知 UI 刷新 |
注意: ObservableCollection 只通知集合变化,不通知集合中对象属性变化。对象属性变化还要靠 INotifyPropertyChanged。
17. 什么是 ICommand?
用来替代按钮点击事件,适合 MVVM。
核心成员:
ExecuteCanExecuteCanExecuteChanged
18. 为什么 MVVM 中推荐用 Command,而不是 Click 事件?
- 事件会让 View 和代码后置耦合
- Command 更符合 MVVM,便于测试和维护
- 按钮是否可用可以通过
CanExecute自动控制
19. 什么是 MVVM?
| 层 | 职责 |
|---|---|
| Model | 数据模型 |
| View | 界面 |
| ViewModel | 界面逻辑和状态 |
通过绑定和命令连接 View 与 ViewModel,减少 UI 和业务耦合。
20. MVVM 的优点是什么?
- 界面和逻辑解耦
- 更易测试
- 更易维护
- 更适合多人协作
- 符合 WPF 的绑定机制
21. Style、ControlTemplate、DataTemplate 的区别?
| 类型 | 作用 |
|---|---|
Style |
设置控件属性、统一外观 |
ControlTemplate |
重定义控件外观结构,不改变数据 |
DataTemplate |
定义数据对象如何显示 |
一句话记忆:
- Style 改属性
- ControlTemplate 改控件长相
- DataTemplate 改数据展示
22. StaticResource 和 DynamicResource 的区别?
| 类型 | 特点 |
|---|---|
StaticResource |
加载时解析,性能更好 |
DynamicResource |
运行时查找,资源变化后可动态更新 |
一般静态资源优先用 StaticResource。
23. Trigger、DataTrigger、EventTrigger 的区别?
| 类型 | 触发条件 |
|---|---|
Trigger |
基于控件属性值 |
DataTrigger |
基于绑定数据值 |
EventTrigger |
基于事件,常配合动画 |
24. Converter 有什么用?
在绑定过程中进行数据转换。
例如:
- bool -> Visibility
- 数字 -> 颜色
- 枚举 -> 文本
接口:
IValueConverterIMultiValueConverter
25. ItemsControl、ListBox、ListView 的区别?
| 控件 | 特点 |
|---|---|
ItemsControl |
最基础的项控件,只负责显示项 |
ListBox |
支持选择 |
ListView |
更强展示能力,常配合 GridView |
26. UserControl 和 CustomControl 的区别?
| 类型 | 适用场景 |
|---|---|
UserControl |
快速组合现有控件,适合业务界面 |
CustomControl |
适合做可复用控件库,支持模板和主题 |
面试标准答法:
- 业务页面用
UserControl - 通用控件库用
CustomControl
27. 什么是模板绑定(TemplateBinding)?
- 在
ControlTemplate中绑定到控件自身属性。 - 比普通绑定更轻量。
- 常见于自定义控件模板中。
28. WPF 中 UI 线程问题怎么处理?
WPF 大多数 UI 对象只能在创建它的线程访问,通常是主线程。后台线程更新 UI,要通过 Dispatcher 切回 UI 线程。
Dispatcher.Invoke(() =>
{
txtMessage.Text = "更新UI";
});29. Dispatcher.Invoke 和 BeginInvoke 的区别?
| 方法 | 特点 |
|---|---|
Invoke |
同步执行,会阻塞当前线程 |
BeginInvoke |
异步执行,不阻塞 |
30. async/await 在 WPF 中有什么注意点?
- 不要在 UI 线程上用
.Result或.Wait(),容易死锁 - 耗时操作放后台线程
- await 后默认会回到 UI 同步上下文,所以可以继续更新 UI
31. WPF 中常见内存泄漏原因有哪些?
- 事件注册后没取消
- 静态事件
- 长生命周期对象持有短生命周期对象引用
- 绑定、计时器、消息订阅没释放
- 可以用
WeakEventManager降低事件引用问题
32. WPF 中如何做输入校验?
常见方式:
ValidationRuleIDataErrorInfoINotifyDataErrorInfo- 异常校验
ValidatesOnExceptions=True
现在更推荐 INotifyDataErrorInfo,支持异步和多错误。
33. 什么是资源字典(ResourceDictionary)?
用来存放样式、模板、画刷、字符串等资源。
可放在:
- App.xaml
- Window / UserControl
- 单独 xaml 文件
可通过 MergedDictionaries 合并多个资源文件。
34. WPF 中常见布局控件有哪些?
| 控件 | 特点 |
|---|---|
Grid |
最常用,行列布局 |
StackPanel |
横向/纵向堆叠 |
DockPanel |
停靠布局 |
WrapPanel |
自动换行 |
Canvas |
绝对定位 |
UniformGrid |
等分网格 |
35. Grid 和 StackPanel 怎么选?
- 复杂界面优先
Grid - 简单纵向/横向排列用
StackPanel StackPanel在滚动、拉伸场景下有时不如Grid灵活
36. 什么是视觉虚拟化(UI Virtualization)?
- 只创建当前可见项的 UI 元素,提升性能。
- 大数据列表很重要。
- 常见依赖
VirtualizingStackPanel。
37. 如何提高 WPF 性能?
- 使用 UI 虚拟化
- 减少视觉树层级
- 优先
StaticResource - 避免频繁刷新整个 UI
- 大量数据时分页/懒加载
- 耗时操作异步处理
- 少用不必要动画和复杂绑定
38. Show 和 ShowDialog 的区别?
| 方法 | 特点 |
|---|---|
Show() |
非模态窗口 |
ShowDialog() |
模态窗口,会阻塞当前窗口交互,直到关闭 |
39. 如何在 WPF 中调试绑定错误?
- 先看 Output 输出窗口
- 绑定错误通常会直接输出
- 常见问题:
- 属性名拼错
- DataContext 不对
- 路径错误
- 转换器异常
40. WPF 中 Loaded、Initialized、Unloaded 的理解?
| 事件 | 说明 |
|---|---|
Initialized |
对象初始化完成 |
Loaded |
控件已加载到可视树,可开始交互 |
Unloaded |
从界面移除时触发 |
第二部分:C# 核心知识
41. 值类型和引用类型的区别?
| 类型 | 特点 | 举例 |
|---|---|---|
| 值类型 | 直接存值 | int、struct、bool、enum |
| 引用类型 | 存对象引用 | class、string、数组 |
值类型通常分配在栈/内联,引用类型对象通常在堆上。
42. string 是值类型还是引用类型?
string是 引用类型- 但它是不可变的(immutable)
43. string 和 StringBuilder 的区别?
| 类型 | 特点 |
|---|---|
string |
不可变,频繁拼接性能差 |
StringBuilder |
适合大量字符串拼接 |
44. abstract 和 interface 的区别?
| 对比项 | abstract | interface |
|---|---|---|
| 是否可有实现 | 可以 | C# 8.0 前不行 |
| 字段 | 可以有 | 不可以 |
| 构造函数 | 可以有 | 不可以 |
| 继承 | 单继承 | 多实现 |
| 语义 | “是什么” | “能做什么” |
45. virtual、override、new 的区别?
| 关键字 | 说明 |
|---|---|
virtual |
父类声明可重写 |
override |
子类真正重写 |
new |
隐藏父类成员,不是重写 |
46. ref 和 out 的区别?
| 关键字 | 说明 |
|---|---|
ref |
传入前必须赋值 |
out |
传入前不用赋值,方法内必须赋值 |
47. 委托和事件的区别?
| 类型 | 特点 |
|---|---|
| 委托 | 方法引用类型 |
| 事件 | 对委托的封装,只能订阅/取消,不能直接外部触发 |
事件更安全,适合发布订阅模式。
48. Func、Action、Predicate 是什么?
| 类型 | 说明 |
|---|---|
Action |
无返回值委托 |
Func |
有返回值委托 |
Predicate<T> |
返回 bool 的委托 |
49. 什么是装箱和拆箱?
- 装箱:值类型 -> object
- 拆箱:object -> 值类型
- 会有性能开销,应尽量避免频繁装拆箱
50. 什么是泛型?有什么好处?
让代码支持多种类型而不牺牲类型安全。
好处:
- 复用
- 类型安全
- 减少装箱拆箱
- 性能更好
51. using 的作用是什么?
- 用于命名空间导入
- 也可用于资源释放,确保调用
Dispose()
52. IDisposable 有什么用?
用来释放非托管资源或重要托管资源。常见如文件流、数据库连接、定时器等。
53. finalizer(析构函数)和 Dispose 的区别?
| 类型 | 特点 |
|---|---|
Dispose |
手动、确定性释放 |
| 析构函数 | GC 不确定时间调用,成本高 |
推荐优先 Dispose。
54. Thread、Task、async/await 的区别?
| 类型 | 特点 |
|---|---|
Thread |
底层线程 |
Task |
对异步操作的更高层封装 |
async/await |
简化异步编程语法 |
现代开发更常用 Task + async/await。
55. 什么是死锁?在 UI 中怎么出现?
两个或多个操作互相等待,导致永远卡住。
WPF 常见是 UI 线程里调用异步任务的 .Result/.Wait() 导致死锁。
56. LINQ 的延迟执行是什么意思?
- 查询语句定义后不立即执行,真正枚举时才执行。
- 如
Where、Select常是延迟执行。 ToList()、ToArray()会立即执行。
57. var、dynamic、object 的区别?
| 类型 | 特点 |
|---|---|
var |
编译期推断类型,本质仍是强类型 |
object |
所有类型基类,调用成员需强转 |
dynamic |
运行时解析,编译器不过多检查,灵活但不安全 |
58. == 和 Equals 的区别?
==:默认比较引用,部分类型会重载成值比较Equals:一般用于比较对象逻辑相等性- 面试回答要看具体类型实现
第三部分:WPF 高频场景题
59. 为什么界面不刷新?
常见原因:
- 属性没实现
INotifyPropertyChanged - 集合用了
List而不是ObservableCollection - DataContext 不对
- 绑定路径写错
- 后台线程直接更新 UI 导致异常
60. 为什么按钮 CanExecute 改了,但界面没更新?
- 没触发
CanExecuteChanged - 或没调用命令重查询逻辑
- 若用 RelayCommand,需要主动 RaiseCanExecuteChanged
61. DataGrid/ListBox 数据很多卡顿怎么办?
- 开启虚拟化
- 分页加载
- 减少复杂模板
- 避免每项都做重计算
- 不要频繁重设
ItemsSource
62. 为什么 TextBox 改值后,ViewModel 没更新?
- 绑定模式可能不是
TwoWay UpdateSourceTrigger可能是LostFocus- 属性没有
set - DataContext 不对
63. ObservableCollection 为什么能更新列表,但项内容变化不刷新?
因为它只通知"集合变化",不通知"项属性变化"。每个项对象还需要实现 INotifyPropertyChanged。
64. UserControl 中 DataContext 为什么容易出问题?
因为 UserControl 里如果自己设置 DataContext = this,会覆盖外部传入的 DataContext。正确做法:谨慎设置,必要时用 RelativeSource 或依赖属性传值。
65. 什么时候用 DependencyProperty,什么时候用 INotifyPropertyChanged?
| 场景 | 选择 |
|---|---|
| 控件开发、需要样式/动画/绑定支持 | DependencyProperty |
| ViewModel 属性通知 | INotifyPropertyChanged |
66. 什么时候用事件,什么时候用命令?
- MVVM 中优先用 命令
- 控件内部行为、底层交互、复杂路由事件可用 事件
67. StaticResource 用得好好的,什么时候必须用 DynamicResource?
主题切换、运行时资源变更、皮肤切换等需要动态刷新的场景。
68. 为什么会选择 MVVM?
为了降低 View 和业务逻辑耦合,提高可维护性、可测试性和复用性。
第四部分:面试高频短答速记
69. WPF 绑定失败首先看什么?
看 Output 输出窗口。
70. TextBox.Text 默认绑定模式是什么?
默认 TwoWay,默认 UpdateSourceTrigger 通常是 LostFocus。
71. Grid.Row 是什么?
附加属性。
72. Button.Click 属于什么事件?
路由事件,一般是 冒泡事件。
73. PreviewMouseDown 属于什么事件?
隧道事件(Tunneling)。
74. DataTemplate 用于什么?
定义"数据对象怎么显示"。
75. ControlTemplate 用于什么?
定义"控件自己长什么样"。
76. ObservableCollection 是否线程安全?
不是线程安全的。跨线程修改常会报错,需要切回 UI 线程或做同步处理。
77. WPF 中跨线程更新 UI 怎么办?
用 Dispatcher。
78. WPF 中最常见架构模式?
MVVM。
第五部分:面试官最爱听的标准短句
1. 为什么用 MVVM?
为了降低 View 和业务逻辑的耦合,提高可维护性、可测试性和复用性。
2. WPF 核心机制有哪些?
数据绑定、依赖属性、路由事件、样式模板、命令机制。
3. 依赖属性有什么用?
让属性能参与绑定、样式、动画和 WPF 属性系统。
4. ObservableCollection 为什么常用?
因为集合增删变化时能自动通知 UI 刷新。
5. WPF 跨线程更新 UI 怎么做?
通过 Dispatcher 切回 UI 线程。
6. 样式和模板区别?
Style 改属性,Template 改结构和外观。
7. 绑定失败怎么看?
先看 Output 窗口的绑定错误信息。
第六部分:15 个必背关键词
- MVVM
- DataContext
- Binding
- TwoWay
- INotifyPropertyChanged
- ObservableCollection
- ICommand
- DependencyProperty
- AttachedProperty
- RoutedEvent
- Style
- ControlTemplate
- DataTemplate
- Dispatcher
- Validation
第七部分:1 分钟自我介绍模板
我主要做 C# 和 WPF 桌面端开发,平时开发中比较常用 MVVM 架构,熟悉数据绑定、命令、依赖属性、路由事件、样式模板、资源字典和异步更新 UI 等内容。项目里也处理过列表性能优化、用户控件封装、输入校验、跨线程 UI 更新等问题。我比较关注代码解耦和可维护性,通常会通过 ViewModel、Command、Converter、ResourceDictionary 等方式优化项目结构。