Какие-либо веские причины не использовать оператор нуль-слияния для отложенной инициализации? - PullRequest
13 голосов
/ 14 сентября 2011

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

Ниже приведен упрощенный пример кода, показывающий более распространенную форму, используемую для отложенной инициализации, а затем одну, использующую оператор объединения нулей.Они имеют точно такие же результаты и выглядят эквивалентными.Мои первые мысли заключаются в том, что после того, как объект был создан, теперь ему присваивается дополнительное назначение с помощью ??.Это не проблема, и компилятор / JIT каким-то образом оптимизирует это, происходит что-то более гнусное, и вам никогда не следует выполнять ленивую инициализацию с помощью ??, или это совершенно безопасно, и из этого не выйдет ничего плохого mojo.

private MyLazyObject _lazyObject;

public MyLazyObject GetMyLazyObjectUsingMoreCommonMethod()
{
    if (_lazyObject != null)
        return _lazyObject;

    _lazyObject = new MyLazyObject();

    return _lazyObject;
}

public MyLazyObject GetMyLazyObjectUsingNullCoalescingOpMethod()
{
    _lazyObject = _lazyObject ?? new MyLazyObject();
    return _lazyObject;
}

Ответы [ 4 ]

14 голосов
/ 14 сентября 2011

Да, небольшая вещь, называемая потокобезопасностью. Два метода, которые вы даете, функционально эквивалентны, так что оператор объединения нулей сам по себе неплох, но ни один из перечисленных вами подходов не является поточно-ориентированным, поэтому, если два потока пытаются вызвать ваш метод Get в в то же время вы можете получить два MyLazyObject с. Это может не иметь большого значения, но, вероятно, это не то, на что вы надеетесь.

Если вы используете .NET 4, просто используйте Lazy.

private Lazy<MyLazyObject> _lazyObject = 
    new Lazy<MyLazyObject>(() => new MyLazyObject());

public MyLazyObject MyLazyObject {get {return _lazyObject.Value;}}

Код является кратким, простым для понимания и безопасным для потоков.

5 голосов
/ 14 сентября 2011

Это совершенно безопасно и четко определено - и действительно, это означает, что компилятор может просто скопировать заголовок стека (dup) и сохранить один раз, а не store-field, load-field.

Единственный раз, когда это проблема, это c # 1.2 (.NET 1.1), где он не существует.

2 голосов
/ 14 сентября 2011

Вы можете даже изменить код первого метода:

public MyLazyObject GetMyLazyObjectUsingMoreCommonMethod()
{
    if (_lazyObject == null)
        _lazyObject = new MyLazyObject();

    return _lazyObject;
}

Это даст тот же IL, что и

public MyLazyObject GetMyLazyObjectUsingNullCoalescingOpMethod()
{
    _lazyObject = _lazyObject ?? new MyLazyObject();

    return _lazyObject;
}

Как уже говорилось, это просто синтаксический сахар.

2 голосов
/ 14 сентября 2011

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

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