Я создаю простую систему частиц на C#/XNA, и, поскольку потенциально каждую секунду будет выполняться большое количество вызовов методов, я хотел убедиться, что точно понимаю, как все работает.
У меня есть класс Particle и Emitter, который:
public sealed class Emitter
{
private struct Particle
{
public Vector2 Position;
public Vector2 Velocity;
public ushort Life;
public bool Alive { get { return (Life > 0); } }
}
private readonly Particle[] _particles;
public Emitter(ushort maxParticles)
{
_particles = new Particle[maxParticles];
}
}
У эмиттера есть другая логика, которая будет создавать, обновлять и отображать частицы, но это не имеет значения. Однако я понимаю, что если бы я вызывал метод, он копировал бы значение при каждом вызове:
public static void UpdateParticle(Particle p)
{
p.Position += p.Velocity;
}
Если бы я сделал Emitter
со 100 000 частиц (как бы маловероятно это ни было), копирование частицы только для обновления, похоже, будет выполнять много ненужной работы. Если бы метод использовал ref вместо UpdateParticle(ref Particle p) { ... }
, тогда он бы просто обращался к данным напрямую и обновлял их там, верно?
Однако, что касается ответа Джона Скита на этот вопрос, он пишет:
Вам почти никогда не нужно использовать ref/out. По сути, это способ получить другое возвращаемое значение, и его обычно следует избегать именно потому, что это означает, что метод, вероятно, пытается сделать слишком много. Это не всегда так (
TryParse
и т. д. являются каноническими примерами разумного использования out), но использование ref/out должно быть относительной редкостью.
Является ли это одним из тех «относительно редких» случаев, когда его использование является правильным выбором?
Что касается моего выбора дизайна для этого класса:
Некоторое время назад я исследовал систему частиц. Я не могу вспомнить точную причину создания
Particle
структуры — что-то о более быстром доступе к непрерывному фрагменту памяти или о меньшем количестве ссылок на объекты — но бенчмаркинг доказывает, что он работает лучше, чем класс.Particle
— это закрытый тип, потому что классEmitter
— это единственное, что когда-либо будет заботиться о нем. Всегда. Клиентский код никогда не должен заботиться об отдельных частицах, только о том, чтоEmitter
создает красивые блестки и визуализирует их._particles
— это фиксированный размер, действующий как пул объектов, потому что повторное использование выделенной памяти должно быть более чувствительным к производительности, чем вызовnew()
.