我有一个很大的结构,我知道从剖析,是昂贵的复制。我正在使用in
关键字传递这个结构的实例,效果很好。
现在我想把它作为一个参数传递给一个迭代器方法,而迭代器方法本身将值传递给其他迭代器方法--但是不允许使用in
,这意味着每次将值传递给一个方法时都会复制该值。
这里的上下文是一个包含视频游戏保存状态的结构,迭代器方法是一个“加载”方法,它在Unity game engine(使用迭代器实现协同)中将保存数据的处理扩展到几个帧上。load方法比较复杂,因此需要将其分解为几种方法。
示例:
struct SaveData{
// large data
}
// Async loading - can spread processing across frames (yay!) but copies lots of data (boo!)
IEnumerator LoadAsync(SaveData saveData) {// wish I could use 'in' here!
// use some part of saveData
yield return;
// use more of saveData
yield return InnerLoad(saveData); // wish I could use 'in' here!
}
IEnumerator InnerLoadAsync(SaveData saveData) {// wish I could use 'in' here!
// use saveData
yield return;
}
// Synchronous loading - very efficient (yay!) but blocks, causing an unacceptably long delay (boo!)
void LoadSynchronous(in SaveData saveData){
// use some part of saveData
// use more of saveData
InnerLoadSynchronous(in saveData);
}
void InnerLoadSynchronous(in SaveData saveData){
// use saveData
}
我理解为什么一般情况下in
不允许用于迭代器(例如,如果迭代器/coroutine的持续时间超过值的所有者,该怎么办?)-所以我可以看到为什么最外层的迭代器函数需要一个副本了。但是对于内部调用,由于它们是用yield return
调用的,所以内部迭代器的使用时间不会超过内部迭代器,所以似乎应该有某种方法在中使用。
这里有没有我遗漏的语言特性,或者我可以使用一个很好的模式来解决它?我认为用一个外部类包装该类型是可行的,但这似乎有点混乱,当然仍然需要一个副本,因为我不能有ref
或in
成员。
但是对于内部调用,由于它们是用yield return调用的,内部迭代器的使用时间不会超过内部,所以似乎应该有一些方法可以使用。
你少了点什么。我们举一个简单的例子:
public class C
{
public static void Main()
{
var enumerator = Outer(3);
Console.WriteLine("Enumerating 1");
enumerator.MoveNext();
Console.WriteLine("Enumerating 2");
enumerator.MoveNext();
var innerEnumerator = (IEnumerator)enumerator.Current;
Console.WriteLine("Enumerating Inner 1");
innerEnumerator.MoveNext();
}
public static IEnumerator Outer(int i)
{
yield return null;
Console.WriteLine("Yielding Inner");
yield return Inner(i);
}
public static IEnumerator Inner(int i)
{
Console.WriteLine($"Inner {i}");
yield break;
}
}
这将打印:
Enumerating 1
Enumerating 2
Yielding Inner
Enumerating Inner 1
Inner 3
(SharpLab)。
正如您所看到的,inner
不是直接枚举的。编译器生成的inner
实现将编译器生成的IEnumerable
返回给outer
的调用方,直到该调用方显式调用MoveNext
时,才执行inner
的主体。
但是,调用inner
的时间要早得多。编译器生成的inner
实现完全执行,并返回生成的IEnumerator
,就在上面的产生inner
之后。因此inner
需要将变量i
存储在编译器生成的类中的某个位置,这就是它不能是in
的原因。