解答“60k”大佬的19道C#面试题(下)

2020-06-11 12:00:27王冬梅

现在了解为啥 F# 签名也能不用写参数了吧,因为参数确实太长了😂

14. 请简述 ref struct 的作用

ref struct 是 C# 7.2 发布的新功能,主要是为了配合 Span<T> ,防止 Span<T> 被误用。

为什么会被误用呢?因为 Span<T> 表示一段连续、固定的内存,可供托管代码和非托管代码访问(不需要额外的 fixed )这些内存可以从 stackalloc 中来,也能从 fixed 中获取托管的位置,也能通过 Marshal.AllocHGlobal() 等方式直接分配。这些内存应该是固定的、不能被托管堆移动。但之前的代码并不能很好地确保这一点,因此添加了 ref struct 来确保。

基于不被托管堆管理这一点,我们可以总结出以下结论:

(1)不能对 ref struct 装箱(因为装箱就变成引用类型了)——包括不能转换为 object 、 dynamic

(2)禁止实现任何接口(因为接口是引用类型)

(3)禁止在 class 和 struct 中使用 ref struct 做成员或自动属性(因为禁止随意移动,因此不能放到托管堆中。而引用类型、 struct 成员和自动属性都可能是在托管内存中)

(4)禁止在迭代器( yield )中使用 ref struct (因为迭代器本质是状态机,状态机是一个引用类型)

(5)在 Lambda 或 本地函数 中使用(因为 Lambda / 本地函数 都是闭包,而闭包会生成一个引用类型的类)

以前常有一个疑问,我们常常说值类型在栈中,引用类型在堆中,那放在引用类型中的值类型成员,内存在哪?(在堆中,但必须要拷到栈上使用)

加入了 ref struct ,就再也没这个问题了。

15. 请简述 ref return 的使用方法

这也是个类似的问题, C# 一直以来就有 值类型 ,我们常常类比 C++ 的类型系统(只有值类型),它天生有性能好处,但 C# 之前很容易产生没必要的复制——导致 C# 并没有很好地享受 值类型 这一优点。

因此 C# 7.0 引入了 ref return ,然后又在 C# 7.3 引入了 ref 参数可被赋值。

使用示例:

Span<int> values = stackalloc int[10086];

values[42] = 10010;
int v1 = SearchValue(values, 10010);
v1 = 10086;
Console.WriteLine(values[42]); // 10010

ref int v = ref SearchRefValue(values, 10010);
v = 10086;
Console.WriteLine(values[42]); // 10086;

ref int SearchRefValue(Span<int> span, int value)
{
	for (int i = 0; i < span.Length; ++i)
	{
		if (span[i] == value)
			return ref span[i];
	}
	return ref span[0];
}

int SearchValue(Span<int> span, int value)
{
	for (int i = 0; i < span.Length; ++i)
	{
		if (span[i] == value)
			return span[i];
	}
	return span[0];
}

注意事项:

(1)参数可以用 Span<T> 或者 ref T

(2)返回的时候使用 return ref val

(3)注意返回值需要加 ref

(4)在赋值时,等号两边的变量,都需要加 ref 关键字( ref int v1 = ref v2 )