czwartek, 6 grudnia 2012

Captured variables

Chciałbym napisać o czymś co się ostatnio dowiedziałem. Kompilator C# używa delegatów nie tylko po to, aby referować do metod, ale również aby przechowywać pewien kontekst - może przechowywać zmienne, aby były dostępne przez różne metody. Przypuśćmy, że mamy taką metodę:
public static bool IsGreaterOrEqualThan(
int value, 
int threshold)
{
 return value >= threshold;
}

Metoda pobiera dwa argumenty. Pierwszy zawiera wartość jaką chcemy sprawdzić, a drugi argument określa wartość progową. Zróbmy z tego predykat - metodę, która zwróci instancje delegata.
public class Pred
{
public static Predicate<int> IsGreaterOrEqualThan(
int threshold)
{
 return value => value >= threshold;
}
}

Wywołanie wygląda następująco:
Predicate greaterorEqualThan100 =
 IsGreaterOrEqualThan(100);

bool isValue1GreaterOrEqual   = greaterorEqualThan100 (1);
bool isValue999GreaterOrEqual = greaterorEqualThan100 (999);

Wartość graniczna (threshold) nie jest argumentem wbudowanej metody (inline method), ale samego predykatu. Kiedy tworzymy instancje predykatu to .NET tworzy dodatkową 'magię'. Kompilator generuje klasę, aby mógł przetrzymywać zmienne. Ta klasa będzie zawierała metodę oraz zmienną. Każde wywołanie metody będzie miało własną wartość zmiennej threshold. Jak pokazuje refleksja, wygenerowana klasa <>c__DisplayClass1 ma nazwę niedozwolona przez c#.
 public class Pred
  {
    public Pred()
    {
      base..ctor();
    }

    public static Predicate<int> 
IsGreaterOrEqualThan(int threshold)
    {
      Pred.<>c__DisplayClass1 cDisplayClass1 = 
new Pred.<>c__DisplayClass1();

      cDisplayClass1.threshold = threshold;

      return new Predicate<int>(
(object) cDisplayClass1, 
__methodptr(<IsGreaterOrEqualThan>b__0));

    }

    [CompilerGenerated]
    private sealed class <>c__DisplayClass1
    {
      public int threshold;

      public <>c__DisplayClass1()
      {
        base..ctor();
      }

      public bool <IsGreaterOrEqualThan>b__0(int value)
      {
        return value >= this.threshold;
      }
    }
  }


Tutaj należy poruszyć dwie ważne kwestie. Uchwycone zmienne (polskie tłumaczenie Captured Variables) mogą spowodować, że kod w pętlach zacznie inaczej działać niż spodziewaliśmy się na samym początku. Po drugie, lokalne zmienne typu ValueType nie zawsze są na stosie (stack) - kompilator kopiuje lokalną zmienną (threshold) do pola obiektu, który znajduje się na stercie (heap). Ta czynność ma bardzo duży wpływ na wydajność aplikacji.

Brak komentarzy:

Prześlij komentarz