Я анализирую OptionsManager.Get(string name):

/// 
/// Returns a configured  instance with the given .
/// 
public virtual TOptions Get(string name)
{
    name = name ?? Options.DefaultName;

    if (!_cache.TryGetValue(name, out TOptions options))
    {
        // Store the options in our instance cache. Avoid closure on fast path by storing state into scoped locals.
        IOptionsFactory localFactory = _factory;
        string localName = name;
        options = _cache.GetOrAdd(name, () => localFactory.Create(localName));
    }

    return options;
}

Что на самом деле означает следующее?

Избегайте замыкания на быстром пути, храня состояние в локалях с масштабированием

Что такое "быстрый путь"?

В чем преимущество создания локальной переменной, указывающей на _factory, по сравнению с простым использованием _factory?

David Klempfner

Ответов: 1

Ответы (1)

@madreflections синопсис в основном верен.

Длинная история

Лямбда - это просто анонимный метод (который, в свою очередь, является просто методом без имени), C# ввел ламбды как лаконичный способ создания анонимных методов. Однако, если анонимному методу необходимо получить доступ к свободным переменным (переменным, которые не являются частью параметров анонимного метода или локальной области видимости), ему необходим способ их перехвата. Это делается с помощью закрытия.

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

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

.

Когда создается закрытие, компилятор не только создает код для класса закрытия, ему необходимо инстанцировать этот класс и назначить любые переменные. В зависимости от объема этих переменных будет принято решение о том, где в коде инициализировать класс и подключить зависимости.

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

Эта эффективность была достигнута с учетом того, что закрытие будет инстанцировано в самой внешней области видимости, которая "нужна" для захвата свободных переменных. Компилятор использует подход fall-back, основанный на области видимости, и это немного сложнее. Однако, присваивая локалы так, как показано в непосредственной внутренней области видимости, компилятор знает, что в этой области видимости безопасно создавать замыкание. Что, в свою очередь, означает, что небольшие накладные расходы на создание ограничены этой ветвью области видимости кодового блока оператора if.

Нелепый пример

.
// instance field
readonly Version _something = new Version();

public virtual void Test1(int someValue)
{ 
   // some weird branch
   if (DateTime.Now == DateTime.MaxValue)
   {
      Method(() => _something.Build + someValue);
   }
}

public virtual void Test2(int someValue)
{
   // some weird branch
   if (DateTime.Now == DateTime.MaxValue)
   {
      // some locals to let the compiler create the closure in the immediate scope
      var localSomething = _something;
      var localValue = someValue;
      Method(() => localSomething.Build + localValue);
   }
}

public virtual void Test3(int someValue)
{
   // some weird branch
   if (DateTime.Now == DateTime.MaxValue)
   {
      // closure needed, it can just create an anonymous method in the class
      // and reference _something directly
      Method(() => _something.Build);
   }
}

// something with a delegate parameter.
private int Method(Func func) => func();

После компиляции

Примечание : Это грубое взаимодействие того, что сделал компилятор. Для всех подробностей посмотрите на этот пример

public class CallingClass
{
   [CompilerGenerated]
   private sealed class Closure1
   {
      public CallingClass copyOfCallingClass;

      public int someValue;

      internal int Method()
      {
         return copyOfCallingClass._something.Build + someValue;
      }
   }

   [CompilerGenerated]
   private sealed class Closure2
   {
      public Version localSomething;

      public int localValue;

      internal int Method()
      {
         return localSomething.Build + localValue;
      }
   }

   [CompilerGenerated]
   private int NewMethod()
   {
      return _something.Build;
   }

   private readonly Version _something = new Version();

   public virtual void Test1(int someValue)
   {
      // generated closure plumbing
      Closure1 Closure1 = new Closure1();
      Closure1.copyOfCallingClass = this;
      Closure1.someValue = someValue;
      if (DateTime.Now == DateTime.MaxValue)
      {
         Method(new Func(Closure1.Method));
      }
   }

   public virtual void Test2(int someValue)
   {
      if (DateTime.Now == DateTime.MaxValue)
      {
         // generated closure plumbing
         Closure2 closure2 = new Closure2();
         closure2.localSomething = _something;
         closure2.localValue = someValue;
         Method(new Func(closure2.Method));
      }
   }

   public virtual void Test3(int someValue)
   {
      if (DateTime.Now == DateTime.MaxValue)
      {
         // pointer to the new generated method
         Method(new Func(NewMethod));
      }
   }

   private int Method(Func func)
   {
      return func();
   }
}

Отказ от ответственности: Хотя замыкания - довольно простая тема, то, как компилятор решает эту задачу и каковы правила ее выполнения, само по себе является нюансом, и поэтому требует глубокого погружения в спецификации. Это было сделано просто как резюме высокого уровня.

.

2022 WebDevInsider