【发布时间】:2021-06-14 22:11:28
【问题描述】:
我有一个从分析中知道的大型结构,复制起来很昂贵。我正在使用 in 关键字传递这个结构的实例,效果很好。
现在我想将此作为参数传递给迭代器方法,该方法本身将值传递给其他迭代器方法 - 但in 是不允许的,这意味着每次传递给方法时都会复制该值。
这里的上下文是一个包含视频游戏保存状态的结构,迭代器方法是一个“加载”方法,它在 Unity 游戏引擎中将保存数据的处理分散到多个帧(它使用迭代器实现协程) .加载方法比较复杂,需要分解成几种方法。
例子:
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 不允许用于迭代器(例如,如果迭代器/协程比值的所有者寿命长怎么办?) - 所以我明白为什么最外层的迭代器函数需要副本。但是对于内部调用,由于它们是用yield return 调用的,所以内部迭代器不会比内部更持久,所以似乎应该有某种方法可以使用in。
这里有没有我遗漏的语言功能,或者我可以使用一个很好的模式来解决它?我认为用外部类包装类型会起作用,但它看起来有点乱,当然仍然需要一份副本,因为我不能有 ref 或 in 成员。
【问题讨论】:
-
如果它这么大,并且你到处都通过引用传递它,你基本上把它当作存储在托管堆以外的某个地方的引用类型。为什么不能转换为引用类型?这一切都在尖叫“值类型是错误的选择。”
-
如果您需要它是一个值类型,您必须将您的
Load方法实现为适当的状态机,而不是依赖编译器为您生成状态机。然后,您可以在每次调用时将in SaveData传递给状态机的MoveNext方法。 -
@madreflection - “存储在托管堆以外的地方”是一个非常有用的说法!你是对的,因为它太大了,所以把它放在堆栈上是很疯狂的。所以很明显,无论如何我应该将它包装在一个引用类型中,我可以在没有'in'的情况下传递它。这并不像在任何地方都使用引用类型那么简单——外部 SaveData 结构将包含许多部分,如果它们都是引用类型,这将导致堆碎片(这是一个真正观察到的问题,因此我为什么这么喜欢结构!)。我的目标是使用少量的大内存区域。
-
@canton7 谢谢!我根本没有考虑过这一点——我会检查 Unity 是否支持接受
MoveNext参数的协程,或者我是否可以自己调用 IEnumerator 方法。我感觉是 Unity 调用了MoveNext,但可能有办法绕过它。 -
Unity确实调用了MoveNext,并且没有办法让 C# 编译器生成一个迭代器方法,该方法具有一个接受任何参数的MoveNext方法。我认为没有必要将SaveData放在包装类中:为什么不把它变成一个类呢?没有人建议它的所有字段也都变成类。