Скажем, у меня есть тип Vector3
с перегруженным оператором *, позволяющий умножить на двойное число:
public readonly struct Vector3
{
public double X { get; }
public double Y { get; }
public double Z { get; }
public Vector3f(double x, double y, double z)
{
X = x;
Y = y;
Z = z;
}
public static Vector3f operator *(in Vector3f v, in double d) => new Vector3f(d * v.X, d * v.Y, d * v.Z);
}
С единственной перегрузкой выражения, похожие на new Vector3(1,2,3) * 1.5
, будут компилироваться, но 1.5 * new Vector3(1,2,3)
будет не. Поскольку векторно-скалярное умножение является коммутативным, я бы хотел, чтобы любой заказ работал, поэтому я добавляю еще одну перегрузку с обращенными параметрами, которая будет просто вызывать исходную перегрузку:
public static Vector3f operator *(in double d, in Vector3f v) => v * d;
Это правильный путь? Должна ли вторая перегрузка быть реализована как
public static Vector3f operator *(in double d, in Vector3f v) => new Vector3f(d * v.X, d * v.Y, d * v.Z);
? Наивно я ожидал бы, что компилятор оптимизирует «лишний» вызов и всегда будет использовать первую перегрузку, если это возможно (или, возможно, заменит тело короткой перегрузки на длинную), но я не знаю поведение компилятор C# достаточно хорош, чтобы сказать в любом случае.
Я понимаю, что во многих случаях это тот тип спора производительности, который затмевается выбором алгоритма, но в некоторых случаях сжатие до последней капли производительности критично , В случаях, критичных к производительности, следует ли реализовывать коммутативные перегрузки операторов в виде двух одинаковых перегрузок, за исключением порядка параметров, или же столь же эффективно иметь один делегат другому?