2017-07-21 94 views
5

Nasza aplikacja serwerowa ma kilka metod, wywoływanych w sekwencji, które przechodzą przez zestaw wyników w wierszu 20-wierszowym i przekształcają go. Każda metoda w tym potoku przechowuje 200-megabajtową kopię danych, z przewidywalnym złym oddziaływaniem pamięci RAM i GC.Wzór do rozbicia C# za pomocą bloków, aby umożliwić programowanie funkcjonalne

Każdy sposób jest podobny wzór:

public HugeCollection1 Step1 (SomeType sourceData) 
{ 
    var transformed = new List<RowType>; 
    using (var foo = InitializeSomethingExpensive(sourceData)) 
    { 
     foreach (var row in foo) 
     { 
      transformed.Add (TransformRow(row)); 
     } 
    } 
    return transformed; 
} 

Następnie te metody nazywane są w rurociągu, np

var results1 = Step1(sourceData); 
var results2 = Step2(results1); 
var results3 = Step3(results2); 
... 
var finalResults = StepN (resultsNMinus1); 
return finalResults; // final results 

Chciałbym przekształcić to do roztworu bardziej funkcjonalne, które iteracji oryginalnych danych źródłowych bez posiadania całego zestawu danych w pamięci RAM. Chcę skończyć z listą ostatecznych wyników bez żadnych kolekcji pośrednich.

Jeśli na każdym etapie rurociągu nie było żadnych ustawień, rozwiązanie byłoby proste: wystarczy przeprowadzić każdą transformację dla każdego wiersza i zapisać tylko wynik końcowy.

var transformed = new List<SmallResult>; 
// TODO: How to set up and ensure teardown of the *other* pipeline steps? 
using (var foo = InitializeSomethingExpensive(sourceData)) 
{ 
    foreach (var row in foo) 
    { 
     object result = row; 
     foreach (var step in Pipeline) 
     { 
      result = step.Transform (result); 
     } 
     transformed.Add (result as SmallResult); 
    } 
} 
return transformed; 

dzisiaj Ale każda z tych oddzielnych etapach rurociągu ma swój własny proces kosztowny konfiguracji i rozdarcie w dół, który jest egzekwowany przez using bloku.

Jaki jest dobry wzór do refaktorowania każdej z tych metod rurociągu, aby zagwarantować, że kod instalacji/zagospodarowania zostanie zagwarantowany? Pseudo-kod, to, że jak się w końcu z tym:

  1. instalacji wszystkie etapy
  2. pętli każdego rzędu
  3. Transform wiersz po każdym etapie
  4. Koniec pętli
  5. czyści wszystkie etapy , gwarantując, że porządki zawsze dzieje
  6. Return (mały) wynika

To nie jest pra ctical, aby połączyć wszystkie bloki używające w jedną metodę, ponieważ kod w każdym z tych kroków jest długi i udostępniony i nie chcę powtarzać tego wspólnego kodu w jednej metodzie.

Wiem, że można ręcznie zastąpić blok using z try/finally, ale robi to ręcznie dla wielu zasobów wydaje się trudniejsze niż to konieczne.

Czy istnieje prostsze rozwiązanie, np. przy użyciu using i yield razem w inteligentny sposób? Czy jest dostępna dobra implementacja klasy "multi-using", która ułatwia ten skoordynowany proces instalacji/rozpadu (np. Jego konstruktor akceptuje listę funkcji, które zwracają IDisposable, a jego implementacja Dispose() zapewniłaby, że wszystko zostanie oczyszczone)?

Wygląda na to, że ktoś mądrzejszy ode mnie już się zorientował, więc zapytaj tutaj przed ponownym wynalezieniem koła.

+0

jestem z trudem tłumacząc twoją uwagę na wielokrotne używanie, ponieważ nie widzę nic więcej niż jeden, używając bloku w twoim kodzie. Wydajność ma wątpliwą wartość, ponieważ osoba dzwoniąca nie musi nigdy przenosić się na koniec sekwencji, czyli tam, gdzie w naturalny sposób nazwiesz Dispose. – hoodaticus

+1

Czy każdy etap rurociągu wymaga własnego 'foo' (który jest kopią danych źródłowych?) – Blorgbeard

+0

Deterministyczna finalizacja jest jednym z tych obszarów, w których C++ obsługuje języki zarządzane. – hoodaticus

Odpowiedz

3

Nie jestem pewien, dlaczego tworzysz tak wiele przedmiotów jednorazowego użytku (można je oczyścić się z metodami podatnego), ale można utworzyć metodę rozszerzenia aby oczyścić ten wzór dla Ciebie

public static class ToolsEx 
{ 
    public static IEnumerable<T> EnumerateAndDispose<X, T>(this X input, 
             Func<X, IEnumerable<T>> func) 
     where X : IDisposable 
    { 
     using (var mc = input) 
      foreach (var i in func(mc)) 
       yield return i; 
    } 
} 

można używać go lubi to ...

var query = from x in new MyClass(0, 0, 2).EnumerateAndDispose(i => i) 
      from y in new MyClass(1, x, 3).EnumerateAndDispose(i => i) 
      select new 
      { 
       x, 
       y, 
      }; 

foreach (var i in query) 
    Console.WriteLine(i); 

... wyjście ...

{ x = 0, y = 0 } 
{ x = 0, y = 1 } 
{ x = 0, y = 2 } 
Disposed: 1/0 
{ x = 1, y = 0 } 
{ x = 1, y = 1 } 
{ x = 1, y = 2 } 
Disposed: 1/1 
Disposed: 0/0 

Oto PIPELI ne przykład z Aggregate ...

var query = from x in new MyClass(0, 0, 2).EnumerateAndDispose(i => i) 
      let r = new MyClass(1, x, 3).EnumerateAndDispose(i => i) 
               .Aggregate(x, (a, i) => (a + i) * 2) 
      select new 
      { 
       x, 
       r, 
      }; 

... a wyniki ...

Disposed: 1/0 
{ x = 0, r = 8 } 
Disposed: 1/1 
{ x = 1, r = 16 } 
Disposed: 0/0 

... klasa test dla przykładu ...

public class MyClass : IEnumerable<int>, IDisposable 
{ 

    public MyClass(int set, int set2, int size) 
    { 
     this.Size = size; 
     this.Set = set; 
     this.Set2 = set2; 
    } 

    public IEnumerator<int> GetEnumerator() 
    { 
     foreach (var i in Enumerable.Range(0, this.Size)) 
      yield return i; 
    } 

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return this.GetEnumerator(); 
    } 

    public void Dispose() 
    { 
     Console.WriteLine("Disposed: {0}/{1}", this.Set, this.Set2); 
    } 

    public int Size { get; private set; } 
    public int Set { get; private set; } 
    public int Set2 { get; private set; } 
} 
+0

btw, po tym możesz zamienić swój potok na '.Aggragate (...)' –