在上篇中,我解析了前 10 道题目,本篇我将尝试解析后面剩下的所有题目。
姐妹篇:解析“60k”大佬的19道C#面试题(上)
这些题目确实不怎么经常使用,因此在后文中,我会提一组我的私房经典“6k面试题”,供大家轻松一刻。
先略看题目:
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 本质是什么?请简述他们的实现机制
解析:
11. 简述 LINQ 的 lazy computation 机制
Lazy computation 是指延迟计算,它可能体现在解析阶段的表达式树和求值阶段的状态机两方面。
首先是解析阶段的表达式树, C# 编译器在编译时,它会将这些语句以表达式树的形式保存起来,在求值时, C# 编译器会将所有的 表达式树 翻译成求值方法(如在数据库中执行 SQL 语句)。
其次是求值阶段的状态机, LINQ to Objects 可以使用像 IEnumemrable<T> 接口,它本身不一定保存数据,只有在求值时,它返回一个迭代器—— IEnumerator<T> ,它才会根据 MoveNext() / Value 来求值。
这两种机制可以确保 LINQ 是可以延迟计算的。
12. 利用 SelectMany 实现两个数组中元素做笛卡尔集,然后一一相加
// 11. 利用 `SelectMany` 实现两个数组中元素的两两相加 int[] a1 = { 1, 2, 3, 4, 5 }; int[] a2 = { 5, 4, 3, 2, 1 }<b>本文来源gao@!dai!ma.com搞$$代^@码!网</b>; a1 .SelectMany(v => a2, (v1, v2) => $"{v1}+{v2}={v1 + v2}") .Dump();
解析与说明:大多数人可能只了解 SelectMany 做一转多的场景(两参数重载,类似于 flatMap ),但它还提供了这个三参数的重载,可以允许你做多对多——笛卡尔集。因此这些代码实际上可以用如下 LINQ 表示:
from v1 in a1 from v2 in a2 select $"{v1}+{v2}={v1 + v2}"
执行效果完全一样。
13. 请为三元函数实现柯里化
解析:柯里化是指将 f(x, y) 转换为 f(x)(y) 的过程,三元和二元同理:
Func<int, int, int, int> op3 = (a, b, c) => (a - b) * c; Func<int, Func<int, Func<int, int>>> op11 = a => b => c => (a - b) * c; op3(4, 2, 3).Dump(); // 6 op11(4)(2)(3).Dump(); // 6
通过实现一个泛型方法,实现通用的三元函数柯里化:
Func<T1, Func<T2, Func<T3, TR>>> Currylize3<T1, T2, T3, TR>(Func<T1, T2, T3, TR> op) { return a => b => c => op(a, b, c); } // 测试代码: var op12 = Currylize3(op3); op12(4)(2)(3).Dump(); // (4-2)x3=6
现在了解为啥 F# 签名也能不用写参数了吧,因为参数确实太长了😂
14. 请简述 ref struct 的作用
ref struct 是 C# 7.2 发布的新功能,主要是为了配合 Span<T> ,防止 Span<T> 被误用。
为什么会被误用呢?因为 Span<T> 表示一段连续、固定的内存,可供托管代码和非托管代码访问(不需要额外的 fixed )这些内存可以从 stackalloc 中来,也能从 fixed 中获取托管的位置,也能通过 Marshal.AllocHGlobal() 等方式直接分配。这些内存应该是固定的、不能被托管堆移动。但之前的代码并不能很好地确保这一点,因此添加了 ref struct 来确保。