CodeSnippet.Cn
代码片段
Csharp
架构设计
.NetCore
西班牙语
kubernetes
MySql
Redis
Algorithm
Ubuntu
Linux
Other
.NetMvc
VisualStudio
Git
pm
Python
WPF
java
Plug-In
分布式
CSS
微服务架构
JavaScript
DataStructure
Shared
C# 面试题 - 大佬版
0
Csharp
小笨蛋
发布于:2023年06月07日
更新于:2023年06月07日
143
#custom-toc-container
### 先略看题目: 1. 请简述`async`函数的编译方式 2. 请简述`Task`状态机的实现和工作机制 3. 请简述`await`的作用和原理,并说明和`GetResult()`有什么区别 4. `Task`和`Thread`有区别吗?如果有请简述区别 5. 简述`yield`的作用 6. 利用`IEnumerable
`实现斐波那契数列生成 7. 简述`stackless coroutine`和`stackful coroutine`的区别,并指出`C#`的`coroutine`是哪一种 8. 请简述`SelectMany`的作用 9. 请实现一个函数`Compose`用于将多个函数复合 10. 实现`Maybe
` `monad`,并利用`LINQ`实现对`Nothing`(空值)和`Just`(有值)的求和 11. 简述`LINQ`的`lazy computation`机制 12. 利用`SelectMany`实现两个数组中元素的两两相加 13. 请为三元函数实现柯里化 14. 请简述`ref struct`的作用 15. 请简述`ref return`的使用方法 16. 请利用`foreach`和`ref`为一个数组中的每个元素加`1` 17. 请简述`ref`、`out`和`in`在用作函数参数修饰符时的区别 18. 请简述非`sealed`类的`IDisposable`实现方法 19. `delegate`和`event`本质是什么?请简述他们的实现机制 ## 解析: ### 1. 请简述`async`函数的编译方式 `async`/`await`是`C# 5.0`推出的异步代码编程模型,其本质是编译为状态机。`只要`函数前带上`async`,`就会`将函数转换为状态机。 ### 2. 请简述`Task`状态机的实现和工作机制 `CPS`全称是`Continuation Passing Style`,在`.NET`中,它会自动编译为: 1. 将所有引用的局部变量做成闭包,放到一个隐藏的`状态机`的类中; 2. 将所有的`await`展开成一个状态号,有几个`await`就有几个状态号; 3. 每次执行完一个状态,都重复回调`状态机`的`MoveNext`方法,同时指定下一个状态号; 4. `MoveNext`方法还需处理线程和异常等问题。 ### 3. 请简述`await`的作用和原理,并说明和`GetResult()`有什么区别 从状态机的角度出发,`await`的本质是调用`Task.GetAwaiter()`的 ``` UnsafeOnCompleted(Action) ``` 回调,并指定下一个状态号。 从多线程的角度出发,如果`await`的`Task`需要在新的线程上执行,该状态机的`MoveNext()`方法会`立即返回`,此时,`主线程被释放出来了`,然后在`UnsafeOnCompleted`回调的`action`指定的线程上下文中继续`MoveNext()`和下一个状态的代码。 而相比之下,`GetResult()`就是在当前线程上立即等待`Task`的完成,在`Task`完成前,当前线程`不会释放`。 > 注意:`Task`也可能不一定在新的线程上执行,此时用`GetResult()`或者`await`就只有会不会创建状态机的区别了。 ### 4. `Task`和`Thread`有区别吗?如果有请简述区别 `Task`和`Thread`都能创建用多线程的方式执行代码,但它们有较大的区别。 `Task`较新,发布于`.NET 4.5`,能结合新的`async/await`代码模型写代码,它不止能创建新线程,还能使用线程池(默认)、单线程等方式编程,在`UI`编程领域,`Task`还能自动返回`UI`线程上下文,还提供了许多便利`API`以管理多个`Task`,用表格总结如下: | 区别 | Task | Thread | | ------------- | ----- | ------ | | `.NET`版本 | `4.5` | `1.1` | | `async/await` | 支持 | 不支持 | | 创建新线程 | 支持 | 支持 | | 线程池/单线程 | 支持 | 不支持 | | 返回主线程 | 支持 | 不支持 | | 管理API | 支持 | 不支持 | `TL;DR`就是,用`Task`就对了。 ### 5. 简述`yield`的作用 `yield`需配合`IEnumerable
`一起使用,能在一个函数中支持多次(不是多个)返回,其本质和`async/await`一样,也是状态机。 如果不使用`yield`,需实现`IEnumerable
`,它只暴露了`GetEnumerator
`,这样确保`yield`是可重入的,比较符合人的习惯。 > 注意,其它的语言,如`C++`/`Java`/`ES6`实现的`yield`,都叫`generator`(生成器),这相当于`.NET`中的`IEnumerator
`(而不是`IEnumerable
`)。这种设计导致`yield`不可重入,`只要其迭代过一次,就无法重新迭代了`,需要注意。 ### 6. 利用`IEnumerable
`实现斐波那契数列生成 ``` IEnumerable
GenerateFibonacci(int n) { int current = 1, next = 1; for (int i = 0; i < n; ++i) { yield return current; next = current + (current = next); } } ``` ### 7. 简述`stackless coroutine`和`stackful coroutine`的区别,并指出`C#`的`coroutine`是哪一种 `stackless`和`stackful`对应的是协程中栈的内存,`stackless`表示栈内存位置不固定,而`stackful`则需要分配一个固定的栈内存。 在`继续执行`(`Continuation`/`MoveNext()`)时,`stackless`需要编译器生成代码,如闭包,来自定义`继续执行`逻辑;而`stackful`则直接从原栈的位置`继续执行`。 性能方面,`stackful`的中断返回需要依赖控制`CPU`的跳转位置来实现,属于骚操作,会略微影响`CPU`的分支预测,从而影响性能(但影响不算大),这方面`stackless`无影响。 内存方面,`stackful`需要分配一个固定大小的栈内存(如`4kb`),而`stackless`只需创建带一个状态号变量的状态机,`stackful`占用的内存更大。 骚操作方面,`stackful`可以轻松实现完全一致的递归/异常处理等,没有任何影响,但`stackless`需要编译器作者高超的技艺才能实现(如`C#`的作者),注意最初的`C# 5.0`在`try-catch`块中是不能写`await`的。 和已有组件结合/框架依赖方面,`stackless`需要定义一个状态机类型,如`Task
`/`IEnumerable
`/`IAsyncEnumerable
`等,而`stackful`不需要,因此这方面`stackless`较麻烦。 `Go`属于`stackful`,因此每个`goroutine`需要分配一个固定大小的内存。 `C#`属于`stackless`,它会创建一个闭包和状态机,需要编译器生成代码来指定`继续执行`逻辑。 总结如下: | 功能 | `stackless` | `stackful` | | ---------- | ----------- | -------------------- | | 内存位置 | 不固定 | 固定 | | 继续执行 | 编译器定义 | CPU跳转 | | 性能/速度 | `快` | 快,但影响分支预测 | | 内存占用 | `低` | 需要固定大小的栈内存 | | 编译器难度 | 难 | `适中` | | 组件依赖 | 不方便 | `方便` | | 嵌套 | 不支持 | `支持` | | 举例 | `C#`/`js` | `Go`/`C++ Boost` | ### 8. 请简述`SelectMany`的作用 相当于`js`中数组的`flatMap`,意思是将序列中的`每一条数据`,转换为`0到多条`数据。 `SelectMany`可以实现过滤/`.Where`,方法如下: ``` public static IEnumerable
MyWhere
(this IEnumerable
seq, Func
predicate) { return seq.SelectMany(x => predicate(x) ? new[] { x } : Enumerable.Empty
()); } ``` `SelectMany`是`LINQ`中`from`关键字的组成部分,这一点将在第`10`题作演示。 ### 9. 请实现一个函数`Compose`用于将多个函数复合 ``` public static Func
Compose
(this Func
f1, Func
f2) { return x => f2(f1(x)); } ``` 然后使用方式: ``` Func
log2 = x => Math.Log2(x); Func
toString = x => x.ToString(); var log2ToString = logCompose(toString); Console.WriteLine(log2ToString(16)); // 4 ``` ### 10. 实现`Maybe
` `monad`,并利用`LINQ`实现对`Nothing`(空值)和`Just`(有值)的求和 本题比较难懂,经过和大佬确认,本质是要实现如下效果: ``` void Main() { Maybe
a = Maybe.Just(5); Maybe
b = Maybe.Nothing
(); Maybe
c = Maybe.Just(10); (from a0 in a from b0 in b select a0 + b0).Dump(); // Nothing (from a0 in a from c0 in c select a0 + c0).Dump(); // Just 15 } ``` 按照我猴子进化来的大脑的理解,应该很自然地能写出如下代码: ``` public class Maybe
: IEnumerable
{ public bool HasValue { get; set; } public T Value { get; set;} IEnumerable
ToValue() { if (HasValue) yield return Value; } public IEnumerator
GetEnumerator() { return ToValue().GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return ToValue().GetEnumerator(); } } public class Maybe { public static Maybe
Just
(T value) { return new Maybe
{ Value = value, HasValue = true}; } public static Maybe
Nothing
() { return new Maybe
(); } } ``` 这种很自然,通过继承`IEnumerable
`来实现`LINQ to Objects`的基本功能,但却是错误答案。 正确答案: ``` public struct Maybe
{ public readonly bool HasValue; public readonly T Value; public Maybe(bool hasValue, T value) { HasValue = hasValue; Value = value; } public Maybe
SelectMany
(Func
> collectionSelector, Func
f) { if (!HasValue) return Maybe.Nothing
(); Maybe
collection = collectionSelector(Value); if (!collection.HasValue) return Maybe.Nothing
(); return Maybe.Just(f(Value, collection.Value)); } public override string ToString() => HasValue ? $"Just {Value}" : "Nothing"; } public class Maybe { public static Maybe
Just
(T value) { return new Maybe
(true, value); } public static Maybe
Nothing
() { return new Maybe
(); } } ``` > 注意: 首先这是一个函数式编程的应用场景,它应该使用`struct`——值类型。 > 其次,不是所有的`LINQ`都要走`IEnumerable
`,可以用手撸的`LINQ`表达式——`SelectMany`来表示。(关于这一点,其实特别重要,我稍后有空会深入聊聊这一点。) ## 总结 > 这些技术平时可能比较冷门,全部能回答正确也并不意味着会有多有用,可能很难有机会用上。 > 但如果是在开发像`ASP.NET Core`那样的超高性能网络服务器、中间件,或者`Unity 3D`那样的高性能游戏引擎、或者做一些高性能实时`ETL`之类的,就能依靠这些知识,做出比肩甚至超过`C`/`C++`的性能,同时还能享受`C#`/`.NET`便利性的产品。
这里⇓感觉得写点什么,要不显得有点空,但还没想好写什么...
返回顶部
About
京ICP备13038605号
© 代码片段 2024