解析“60k”大佬的19道C#面试题(上)
解析“60k”大佬的19道C#面试题(上)
先略看题目:
请简述
async
函数的编译方式请简述
Task
状态机的实现和工作机制请简述
await
的作用和原理,并说明和GetResult()
有什么区别Task
和Thread
有区别吗?如果有请简述区别简述
yield
的作用利用
IEnumerable<T>
实现斐波那契数列生成简述
stackless coroutine
和stackful coroutine
的区别,并指出C#
的coroutine
是哪一种请简述
SelectMany
的作用请实现一个函数
Compose
用于将多个函数复合实现
Maybe<T>
monad
,并利用LINQ
实现对Nothing
(空值)和Just
(有值)的求和简述
LINQ
的lazy computation
机制利用
SelectMany
实现两个数组中元素的两两相加请为三元函数实现柯里化
请简述
refstruct
的作用请简述
refreturn
的使用方法请利用
foreach
和ref
为一个数组中的每个元素加1
请简述
ref
、out
和in
在用作函数参数修饰符时的区别请简述非
sealed
类的IDisposable
实现方法delegate
和event
本质是什么?请简述他们的实现机制
没错,这是一位来自【广州.NET技术俱乐部】微信群的偏 ProgrammingLanguages
( 编程语言开发科学
)的大佬,本文我将斗胆回答一下这些题目????。
由于这些题目(对我来说)比较难,因此我这次只斗胆回答前 10
道题,发作上篇,另外一半的题目再等我慢慢查阅资料,另行回答????。
解析:
1. 请简述 async
函数的编译方式
async
/ await
是 C# 5.0
推出的异步代码编程模型,其本质是编译为状态机。只要函数前带上 async
,就会将函数转换为状态机。
2. 请简述 Task
状态机的实现和工作机制
CPS
全称是 ContinuationPassingStyle
,在 .NET
中,它会自动编译为:
将所有引用的局部变量做成闭包,放到一个隐藏的
状态机
的类中;将所有的
await
展开成一个状态号,有几个await
就有几个状态号;每次执行完一个状态,都重复回调
状态机
的MoveNext
方法,同时指定下一个状态号;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
较新,发布于 .NET4.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<T>
一起使用,能在一个函数中支持多次(不是多个)返回,其本质和 async/await
一样,也是状态机。
如果不使用 yield
,需实现 IEnumerable<T>
,它只暴露了 GetEnumerator<T>
,这样确保 yield
是可重入的,比较符合人的习惯。
注意,其它的语言,如
C++
/Java
/ES6
实现的yield
,都叫generator
(生成器),这相当于.NET
中的IEnumerator<T>
(而不是IEnumerable<T>
)。这种设计导致yield
不可重入,只要其迭代过一次,就无法重新迭代了,需要注意。
6. 利用 IEnumerable<T>
实现斐波那契数列生成
IEnumerable<int> GenerateFibonacci(int n)
{if (n >= 1) yield return 1;int a = 1, b = 0;for (int i = 2; i <= n; ++i){int t = b;b = a;a += t;yield return a;}
}
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<T>
/ IEnumerable<T>
/ IAsyncEnumerable<T>
等,而 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<T> MyWhere<T>(this IEnumerable<T> seq, Func<T, bool> predicate)
{return seq.SelectMany(x => predicate(x) ? new[] { x } : Enumerable.Empty<T>());
}
SelectMany
是 LINQ
中 from
关键字的组成部分,这一点将在第 10
题作演示。
9. 请实现一个函数 Compose
用于将多个函数复合
public static Func<T1, T3> Compose<T1, T2, T3>(this Func<T1, T2> f1, Func<T2, T3> f2)
{return x => f2(f1(x));
}
然后使用方式:
Func<int, double> log2 = x => Math.Log2(x);
Func<double, string> toString = x => x.ToString();
var log2ToString = log2.Compose(toString);
Console.WriteLine(log2ToString(16)); // 4
10. 实现 Maybe<T>
monad
,并利用 LINQ
实现对 Nothing
(空值)和 Just
(有值)的求和
本题比较难懂,经过和大佬确认,本质是要实现如下效果:
void Main()
{Maybe<int> a = Maybe.Just(5);Maybe<int> b = Maybe.Nothing<int>();Maybe<int> 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<T> : IEnumerable<T>
{public bool HasValue { get; set; }public T Value { get; set;}IEnumerable<T> ToValue(){if (HasValue) yield return Value;}public IEnumerator<T> GetEnumerator(){return ToValue().GetEnumerator();}IEnumerator IEnumerable.GetEnumerator(){return ToValue().GetEnumerator();}
}
public class Maybe
{public static Maybe<T> Just<T>(T value){return new Maybe<T> { Value = value, HasValue = true};}public static Maybe<T> Nothing<T>(){return new Maybe<T>();}
}
这种很自然,通过继承 IEnumerable<T>
来实现 LINQ toObjects
的基本功能,但却是错误答案。
正确答案:
public struct Maybe<T>
{public readonly bool HasValue;public readonly T Value;public Maybe(bool hasValue, T value){HasValue = hasValue;Value = value;}public Maybe<B> SelectMany<TCollection, B>(Func<T, Maybe<TCollection>> collectionSelector, Func<T, TCollection, B> f){if (!HasValue) return Maybe.Nothing<B>();Maybe<TCollection> collection = collectionSelector(Value);if (!collection.HasValue) return Maybe.Nothing<B>();return Maybe.Just(f(Value, collection.Value));}public override string ToString() => HasValue ? $"Just {Value}" : "Nothing";
}
public class Maybe
{public static Maybe<T> Just<T>(T value){return new Maybe<T>(true, value);}public static Maybe<T> Nothing<T>(){return new Maybe<T>();}
}
注意:首先这是一个函数式编程的应用场景,它应该使用 struct
——值类型。
其次,不是所有的 LINQ
都要走 IEnumerable<T>
,可以用手撸的 LINQ
表达式—— SelectMany
来表示。(关于这一点,其实特别重要,我稍后有空会深入聊聊这一点。)
总结
这些技术平时可能比较冷门,全部能回答正确也并不意味着会有多有用,可能很难有机会用上。
但如果是在开发像 ASP.NETCore
那样的超高性能网络服务器、中间件,或者 Unity3D
那样的高性能游戏引擎、或者做一些高性能实时 ETL
之类的,就能依靠这些知识,做出比肩甚至超过 C
/ C++
的性能,同时还能享受 C#
/ .NET
便利性的产品。
群里有人戏称面试时出这些题的公司,要么是心太大,要么至少得开
60k
,因此本文取名为60k大佬
。
敬请期待我的下篇????。
解析“60k”大佬的19道C#面试题(上)相关推荐
- 解析“60k”大佬的19道C#面试题(下)
解析"60k"大佬的19道C#面试题(下) 在上篇中,我解析了前 10 道题目,本篇我将尝试解析后面剩下的所有题目. 解析"60k"大佬的19道C#面试题(上) ...
- 多个线程访问统一对象的不同方法_不会多线程还想进BAT?精选19道多线程面试题,有答案边看边学...
一. Java程序如何停止一个线程? 建议使用"异常法"来终止线程的继续运行.在想要被中断执行的线程中, 调用 interrupted()方法,该方法用来检验当前线程是否已经被中断 ...
- 2021-02-252021 互联网大厂年度总结1000+道高频面试题(附答案解析)冲刺2021
2021 互联网大厂年度总结1000+道高频面试题(附答案解析)冲刺2021 1.String类能不能被继承?为什么? 不能 因为string类是被final修饰的类,final修饰过的类不能被继承. ...
- 19道小米网运维工程师笔试真题
<19道小米网运维工程师笔试真题,你能通关吗?> 题目转至:马哥Linux运维 第一部分:Linux基础 题目1: 有一百个图片文件,它们的地址都是 http://down.xiaomi. ...
- 收藏!Java 500 道核心面试题全解析
每个技术人都有个大厂梦,我觉得这很正常,并不是饭后的谈资而是每个技术人的追求.像阿里.腾讯.美团.字节跳动.京东等等的技术氛围与技术规范度还是要明显优于一些创业型公司/小公司,如果说能够在这样的公司锻 ...
- 【面试题】104道 CSS 面试题,助你查漏补缺(下)
作者:CavsZhouyou https://github.com/CavsZhouyou/Front-End-Interview-Notebook/blob/master/Css/Css.md 本部 ...
- 精选30道Java笔试题解答
精选30道Java笔试题解答 都是一些非常非常基础的题,是我最近参加各大IT公司笔试后靠记忆记下来的,经过整理献给与我一样参加各大IT校园招聘的同学们,纯考Java基础功底,老手们就不用进 ...
- 经过阿里,百度一面,二面后,我总结了150道iOS面试题
经过阿里,百度一面,二面后,我总结了150道iOS面试题. 金三银四已经过去,根据统计,很多人都会选择在三月四月跳槽,原因有很多,企业年后会有大量员工离职,员工觉得老公司待遇不怎么样?薪资不够高,想换 ...
- 68 道Redis面试题
转载来源 : https://mp.weixin.qq.com/s/As9MYcFV7Rk_NR5lKBQyzQ 作者 | ThinkWon 责编 | 徐威龙 来源 | CSDN 博客 介绍 Redi ...
最新文章
- 晴天计算机按键,电脑怎样才能在按键的时候,按下去按键没反应,手台起来才有呢?...
- golang中的strings.Repeat
- java web如何配置ask_Javaweb新手之路之JavaWeb开发环境配置篇
- Java处理split分割【for循环】
- java转成图形界面_【转】java图形界面设计(AWT)
- 17 - 引用类型比较内容
- oracle怎么扩大连接数,如何增加ORACLE连接数
- 路飞学城14天集训营作业2—三级菜单
- 好的文案,极大的降低沟通成本
- mysql monitor怎么用_MySQL 监控工具 mysql-monitor 详解
- java如何使用live2d_小白教程之给网页添加Live2D
- Eclipse SVN插件Subclipse和Subversive简介
- python科研向数据处理篇——python-pptx批量向PPT中插入图片
- jQuery中$()函数有几种用法
- 做phodal的御用编辑,其实我是拒绝的
- 【Java网络编程】:Netty实现OIO和NIO
- java小组口号,小组口号霸气
- openCV 实现用 python 画线、画矩形、画圆、画椭圆、画多边形
- 企业IT管理岗的首选认证:ITIL®4 Foundation
- java游戏开发入门(十) -粒子特效
热门文章
- Teams与OneDrive for Business和SharePoint的关系
- 数据安全 数据销毁_如何安全销毁敏感数据CD / DVD?
- NetCore2.0Web应用之Startup
- Unity 游戏框架搭建 (九) 减少加班利器-QConsole
- Dubbo源码解析之Zookeeper连接
- 物联网(车联网)平台架构方案
- 正则表达式匹配字符串的问题
- ssl certificate 验证
- (转)64bit上安装32位oracle 10 g出现错误:无法定位承诺工序输入点 getprocessimagifilenamew 于动态链接库PSAPI.DLL...
- ABP vNext微服务架构详细教程——结束语