Копирует ли передача изменяемой структуры в качестве параметра in через несколько методов?

Я хочу свести к минимуму копирование структур в математической библиотеке и прочитать о C# 7.2 inмодификатор, особенно предупреждения при его использовании с изменяемыми структурами.

Так получилось, что у меня есть эта изменяемая структура:

public struct Quaternion
{
    public float W;
    public float X;
    public float Y;
    public float Z;
}

Пока что в библиотеке есть такие методы, где параметры передаются 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;

Из документации MSDN я узнал, что если я изменю их на inпараметры, пока я обращаюсь только к полям изменяемой структуры, никакой защитной копии не произойдет, поскольку компилятор видит, что я не изменяю изменяемую структуру:

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;

Первый вопрос: правильно ли я понимаю такое поведение?

Во-вторых, глупый вопрос: если в одном из таких методов, которые принимают структуру какin будет ли компилятор скопировать его, если я вызову другой метод, принимающий их как inпараметры? Пример:

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();
}

Я почти уверен, что он не должен создавать копии - как еще тогда я мог бы предотвратить копии со стороны вызова? Но поскольку я не уверен, лучше сначала спросить, прежде чем создавать беспорядок.


Дополнение

Причины, по которым я хочу изменить ref к in:

  • (static) readonly поля (например, определенные постоянные кватернионы) не могут быть переданы как ref аргументы.
  • Я не могу указать ref по параметрам оператора, но я могу использовать in.
  • Постоянно уточняя ref на сайте звонка некрасиво.
  • Я знаю, что мне нужно везде менять сайт вызова, но это нормально, поскольку эта библиотека будет использоваться только для внутренних целей.

1 ответ

Как упоминалось в комментариях, использование for параметров изменяемых структур может создавать защитные копии, если среда выполнения не может гарантировать, что переданный экземпляр не будет изменен. Это может быть трудно гарантировать, если вы вызываете свойства, индексаторы или методы для этого экземпляра.

Следовательно, всякий раз, когда вы не собираетесь изменять экземпляр таким образом, вы должны явно заявить об этом, сделав их . Преимущество этого заключается в том, что компиляция завершается сбоем, если вы пытаетесь изменить экземпляр в них.

Обратите внимание на размещениеreadonlyключевое слово в следующих примерах, особенно:

      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);
}

Теперь, когда у вас есть метод, использующийVec2сinмодификатор, вы можете безопасно вызывать вышеуказанное без создания копии.

( Эта функция была введена в С# 8.0 и недоступна, когда я задал вопрос.)

Другие вопросы по тегам