Должен ли аксессор C # использовать приватную переменную или вычислять на лету? - PullRequest
3 голосов
/ 24 августа 2010

Какая практика программирования лучше и почему?

У меня есть такой класс:

class data {

    public double time { get; internal set; }
    public double count { get; internal set; }
    public average_count { ... }

}

Где Average_count должен быть read_only и давать расчет количества / времени.

Лучше написать аксессор как:

public average_count { get {

    return (time == 0) ? 0 : (count / time);

}}

Или я должен сделать что-то вроде:

private _avg_count;

public average_count {
    get 
    {
        return _avg_count;
    }

    internal set
    {
        return _avg_count;
    }
}

Где _avg_count обновляется, когда во времени и счетчике установлены аксессоры?

Кажется, что первое легче читать, но оно может быть медленнее, если часто обращаться к average_count. Будет ли оптимизация компилятора иметь значение незначительным?

Ответы [ 8 ]

13 голосов
/ 24 августа 2010

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

5 голосов
/ 24 августа 2010

Это такой, казалось бы, простой вопрос, но на него почти невозможно ответить. Причина в том, что то, что «правильно», зависит от ряда факторов.

1. Производительность

В своем ответе Skilldrick рекомендует, чтобы вы предпочитали то, что читается, а не то, что работает лучше всего, как общее правило:

[R] пригодность и ремонтопригодность должны только приносится в жертву ради исполнения когда это абсолютно необходимо.

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

Так что это призыв к суду с вашей стороны. Лучший совет - вести профиль всякий раз, когда у вас есть подозрения; и если в вашем случае целесообразно жертвовать удобочитаемостью ради производительности, то вам следует это сделать.

2. Использование памяти

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

Недостатком этого подхода, конечно, является то, что для его обслуживания требуется больше памяти. int? требует по существу того же объема памяти, что и int плюс bool (что из-за проблем с выравниванием может фактически означать 64 бита вместо 40). Если у вас есть множество экземпляров этого класса data, а память является дефицитным ресурсом на платформе, на которую вы ориентируетесь, увеличение числа типов на 32 бита на экземпляр может оказаться не самым умным ходом.

3. Ремонтопригодность

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

Суть в том, что только вы знаете свои точные обстоятельства и, следовательно, вы в лучшем положении, чтобы решить, что делать здесь.

4 голосов
/ 24 августа 2010

В случаях, когда производительность критична, и вам необходимо часто обращаться к свойству, у вас также есть другой вариант: вычисление результата, когда оно необходимо, и затем кеширование результата. Шаблон будет выглядеть так:

class Foo
{
    int? _cachedResult = null;

    int _someProperty;
    public int SomeProperty
    {
        get { return _someProperty; }
        set { _someProperty = value; _cachedResult = null; }
    }

    int _someOtherProperty;
    public int SomeOtherProperty
    {
        get { return _someOtherProperty; }
        set { _someOtherProperty = value; _cachedResult = null; }
    }

    public int SomeDerivedProperty
    {
        get
        {
            if (_cachedResult == null)
                _cachedResult = someExpensiveCalculation();

            return (int)_cachedResult;
        }
    }
}
4 голосов
/ 24 августа 2010

Зависит от того, как часто вы будете звонить average_count и как часто будут изменены count и time. Для такой оптимизации я бы предложил использовать профилировщик.

1 голос
/ 24 августа 2010

Я бы пошел с вашим первым вариантом. Это объекты в памяти, поэтому вычисление того, что вы делаете, будет чрезвычайно быстрым. Кроме того, если вы создали для этого выделенное свойство (например, average_count), вам нужно будет добавить код more , чтобы пересчитать его в установщике как для времени, так и для счета.

В качестве дополнительного примечания (поскольку вы спрашиваете о передовой практике), вам следует использовать оболочку Pascal в C #, которая является начальными заглавными буквами и не подчеркивает.

1 голос
/ 24 августа 2010

В этом простом случае я бы определенно реализовал сначала рассчитанную версию;затем оптимизируйте, если необходимо.Если вы сохраните значение, вам также потребуется дополнительный код для пересчета значения, если любое из значений, от которых оно зависит, изменится, что приведет к ошибкам.

Не оптимизируйте преждевременно.

0 голосов
/ 24 августа 2010

Если вы заботитесь о безопасности потоков, возможно, будет проще сделать второй вариант, чем первый.

private double _avg_count;
static readonly object avgLock = new object();

public double time { get; internal set; }
public double count { get; internal set; }


public double average_count {
    get 
    {
        return _avg_count;
    }


}

private void setAverageCount()
{
    _avg_count = time == 0 ? 0 : (count / time);
}


 public void Increment()
 {
     lock (avgLock)
     {
         count += 1;
         setAverageCount();
     }


 }


 public void EndTime(double time)
 {
     lock (avgLock)
     {
         time = time;
         setAverageCount();

     }
 }
0 голосов
/ 24 августа 2010

Будет ли оптимизация компилятора незначительной разницей?

Зависит от того, что вы считаете "значительным".Чтение varaible довольно быстро.Разделить два числа довольно быстро.Фактически, в зависимости от того, что находится в кэш-памяти RAM, чтение переменных может занять больше времени, чем деление.

Используйте первый метод.Если это кажется медленным, то рассмотрим второе.

...