I want to minimize copying of structs in a maths library and read about the C# 7.2 in
modifier, especially the warnings when using it with mutable structs.
It so happens that I have this mutable struct:
public struct Quaternion
{
public float W;
public float X;
public float Y;
public float Z;
}
So far, the library has methods like this, where parameters are passed by ref
:
public static void Dot(ref Quaternion left, ref Quaternion right, out float result)
=> result = left.W * right.W + left.X * right.X + left.Y * right.Y + left.Z * right.Z;
From the MSDN documentation, I learned that if I change these to in
parameters, as long as I only access fields of a mutable struct, no defensive copy will occur since the compiler sees I am not modifying the mutable struct:
public static void Dot(in Quaternion left, in Quaternion right, out float result)
=> result = left.W * right.W + left.X * right.X + left.Y * right.Y + left.Z * right.Z;
First question: Is my understanding of that behavior correct?
Second, silly question: If in one of such methods which accept the struct as an in
parameter, will the compiler copy it if I call another method accepting them as in
parameters? An example:
public static void Lerp(in Quaternion start, in Quaternion end, float amount,
out Quaternion result)
{
float inv = 1.0f - amount;
if (Dot(start, end) >= 0.0f) // will 2 copies be created here?
{
result.W = inv * start.W + amount * end.W;
result.X = inv * start.X + amount * end.X;
result.Y = inv * start.Y + amount * end.Y;
result.Z = inv * start.Z + amount * end.Z;
}
else
{
result.W = inv * start.W - amount * end.W;
result.X = inv * start.X - amount * end.X;
result.Y = inv * start.Y - amount * end.Y;
result.Z = inv * start.Z - amount * end.Z;
}
result.Normalize();
}
I am pretty sure it should not create copies - how else would I prevent copies from the call side then? But as I am not sure, I better ask first before creating a mess.
Addendum
Reasons I want to change ref
to in
:
static
) readonly
fields (e.g. specific constant quaternions) cannot be passed as ref
arguments.ref
on operator parameters, but I can use in
.ref
on the call site is ugly.As mentioned in the comments, using in
for parameters of mutable structs can create defensive copies if the runtime cannot guarantee that the passed instance is not modified. It may be hard to guarantee this if you call properties, indexers, or methods on that instance.
Ergo, whenever you do not intend to modify the instance in such, you should state this explicitly by making them readonly
. This has the benefit of also causing compilation to fail if you attempt to modify the instance in them.
Note the placement of the readonly
keyword in the following examples especially:
public struct Vec2
{
public float X, Y;
// Properties
public readonly float Length
{
get { return MathF.Sqrt(LengthSq); }
}
public readonly float LengthSq => X * X + Y * Y;
// Indexers (syntax the same for properties if they also have setter)
public float this[int index]
{
readonly get => index switch
{
0 => X,
1 => Y,
_ => throw ...
};
set
{
switch (index)
{
case 0: X = value; break;
case 1: Y = value; break;
default: throw ...
}
}
}
// Methods
public readonly override int GetHashCode() => HashCode.Combine(X, Y);
}
Now, whenever you have a method using Vec2
with the in
modifier, you can safely call the above without a copy being made.
(This feature was introduced in C# 8.0 and not available when I asked the question.)