如何解决如果我有一个c#只读结构,其成员具有非只读结构,则编译器将使用in参数
因此,例如,如果我有一个结构PlayerData,它具有在System.Numerics中定义的Vector3结构的成员(不是只读结构)
public readonly struct PlayerData
{
public readonly Vector3 SpawnPoint;
public readonly Vector3 CurrentPosition;
public readonly Vector3 Scale;
...constructor that initializes fields
}
然后将它们传递给方法:
AddplayerData(new PlayerData(Vector3.Zero,Vector3.UnitX,Vector3.One))
public void AddplayerData(in PlayerData playerData)
{
...
}
由于Vector3成员不是只读结构,而是只读字段,因此c#会创建防御性副本吗?现有的库不提供Vector的只读版本,因此如果我不为基本的Vector编写自己的版本,那么在传递大于intptr的结构时,我是否被迫在参数优化中忘记了整个过程?阅读时,关于用法的信息不清楚:https://docs.microsoft.com/en-us/dotnet/csharp/write-safe-efficient-code
解决方法
有趣的问题。让我们测试一下:
public void AddPlayerData(in PlayerData pd)
{
pd.SpawnPoint.X = 42;
}
给出编译器错误:
只读字段'PlayerData.SpawnPoint'的成员无法修改(除非在构造函数或变量中
据此,我认为编译器将不会创建任何防御性副本,因为无论如何都无法修改非只读结构。可能会有一些情况允许结构改变,但我对语言规范的了解还不足以确定这一点。
但是,讨论编译器优化是很困难的,因为只要结果相同,编译器通常就可以自由地执行其想要的任何事情,因此,行为在编译器版本之间可能会发生很大变化。与往常一样,建议您做一个基准来比较您的替代品。
那么,让我们这样做吧
public readonly struct PlayerData1
{
public readonly Vector3 A;
public readonly Vector3 B;
public readonly Vector3 C;
public readonly Vector3 D;
public readonly Vector3 E;
public readonly Vector3 F;
public readonly Vector3 G;
public readonly Vector3 H;
}
public readonly struct PlayerData2
{
public readonly ReadonlyVector3 A;
public readonly ReadonlyVector3 B;
public readonly ReadonlyVector3 C;
public readonly ReadonlyVector3 D;
public readonly ReadonlyVector3 E;
public readonly ReadonlyVector3 F;
public readonly ReadonlyVector3 G;
public readonly ReadonlyVector3 H;
}
public readonly struct ReadonlyVector3
{
public readonly float X;
public readonly float Y;
public readonly float Z;
}
public static float Sum1(in PlayerData1 pd) => pd.A.X + pd.D.Y + pd.H.Z;
public static float Sum2(in PlayerData2 pd) => pd.A.X + pd.D.Y + pd.H.Z;
[Test]
public void TestInParameterPerformance()
{
var pd1 = new PlayerData1();
var pd2 = new PlayerData2();
// Do warmup
Sum1(pd1);
Sum2(pd2);
float sum1 = 0;
var sw1 = Stopwatch.StartNew();
for (int i = 0; i < 1000000000; i++)
{
sum1 += Sum1(pd1);
}
float sum2 = 0;
sw1.Stop();
var sw2 = Stopwatch.StartNew();
for (int i = 0; i < 1000000000; i++)
{
sum2 += Sum2(pd2);
}
sw2.Stop();
Console.WriteLine("Sum1: " + sw1.ElapsedMilliseconds);
Console.WriteLine("Sum2: " + sw2.ElapsedMilliseconds);
}
对我来说,使用.Net framework 4.8可以
Sum1: 1035
Sum2: 1027
即完全在测量误差之内。所以我不用担心。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。