2009-10-19 11 views
6

Mam rekurencyjne wywołanie metody. Kiedy zostanie zgłoszony wyjątek, chciałbym zobaczyć, gdzie w rekursywnym stosie wywołań się stało. Mam pole, które zawiera "ścieżkę", która reprezentuje stos rekursji.C#: Obsługa wyjątków w rekursywnym wywołaniu

Teraz chciałbym dodać informacje o ścieżce do każdego wyjątku, który mógłby zostać zgłoszony w wywołaniu rekursywnym.

void Recursive(int x) 
{ 
    // maintain the recursion path information 
    path.Push(x); 

    try 
    { 
    // do some stuff and recursively call the method 
    Recursive(x + 6); 
    } 
    catch(Exception ex) 
    { 
    if (ex is RecursionException) 
    { 
     // The exception is already wrapped 
     throw; 
    } 
    // wrap the exception, this should be done only once. 
    // save the path and original exception to the wrapper. 
    throw new RecursionException(path.ToString(), ex); 
    } 
    finally 
    { 
    // maintain the recursion path information 
    path.Pop() 
    } 
} 

To po prostu wygląda na zbyt skomplikowane. Istnieje nie tylko jedna metoda. Prawdopodobnie istnieje dwadzieścia lub nawet więcej miejsc, w których musiałem napisać ten kod.

Czy istnieje prostszy sposób wdrożenia tego?


Edit: Aby to podkreślić: Chciałbym mieć dużo prostszą sytuację, gdzie nie ma takich napowietrznych rekursywnie wywołać metodę, bo mam wiele takich rekurencyjnych wywołań, istnieje nie tylko jedna metoda, istnieje kilka metod rekurencyjnie wywołujących się nawzajem, które są wystarczająco złożone.

Chciałbym więc uniknąć całego bloku try-catch, ale nie widzę na to żadnego rozwiązania.

To nie jest duży problem dla wyjątków wyrzuconych w moim własnym kodzie, ponieważ może zawierać ścieżkę od początku. Ale jest to problem z każdym innym wyjątkiem.


Edit: Wyjątki muszą być opakowane w każdym innym kodem, a nie tylko wtedy, gdy wywołanie metody rekurencyjnej:

try 
    { 
    int a = 78/x; // DivisionByZeroExeption   

    Recursive(x + 6); 

    this.NullReference.Add(x); // NullReferenceException 
    } 

Więc owijania tylko wezwanie do Recusive nie działa.

Istnieje wiele takich metod, mających różne podpisy, robiąc różne rzeczy, jedyną popularną rzeczą jest obsługa wyjątków.

+2

Jaka jest różnica między "ścieżką" i właściwością "callstack", która występuje w Exception? –

+0

Zestaw wywoławczy składa się tylko z nazw metod, które są zawsze takie same. W tym przypadku byłby to "Rekursywny -> Rekurencyjny -> Rekurencyjny -> Rekurencyjny", który nic nie mówi. Rekurencja faktycznie schodzi do drzewa, a ścieżka reprezentuje ścieżkę do bieżącej gałęzi w postaci czytelnej dla człowieka. –

+0

Wykonanie całej pracy metodami, które są przekazywane do programu Recursive, byłoby zapakowane w wyjątki przy użyciu mojej rozszerzonej metody, obsługa różnych sygnatur jest trudna, chociaż możesz być w stanie uciec z używaniem generycznych do określenia typów parametrów, ale arbitralne podpisy mogą nie będzie możliwe bez pisania przeciążeń dla każdego ... –

Odpowiedz

2

Myślę, że próbujesz uwzględnić ścieżkę rekurencyjną w szczegółach wyjątku, aby ułatwić debugowanie.

Co z wypróbowaniem tego.

public void Recursive(int x) 
{ 
    try 
    { 
    _Recursive(x) 
    } 
    catch 
    { 
    throw new RecursionException(path.ToString(), ex); 
    clear path, we know we are at the top at this point 
    } 
} 

private void _Recursive(int x) 
{ 
    // maintain the recursion path information 
    path.Push(x); 

    _Recursive(x + 6); 

    //maintain the recursion path information 
    //note this is not in a catch so will not be called if there is an exception 
    path.Pop() 
} 

Jeśli używasz gwintowania itp, będzie można spojrzeć na przechowywanie ścieżki w pamięć lokalna wątku.


Jeśli nie chcesz, aby zmusić rozmówcę do czynienia z RecursionException, można dokonać „ścieżka” społeczeństwu tak dzwoniący do niego dostęp. (Jak zwykle Eric Lippert odpowie później)

Albo możesz zalogować ścieżkę do swojego systemu rejestrowania błędów, gdy przechwycisz wyjątek, a następnie po prostu ponownie wyrzucisz wyjątek.

public void Recursive(int x) 
{ 
    try 
    { 
    _Recursive(x) 
    } 
    catch 
    { 
    //Log the path to your loggin sysem of choose 
    //Maybe log the exception if you are not logging at the top 
    // of your applicatoin   
    //Clear path, we know we are at the top at this point 
    } 
} 

Ma to tę zaletę, że osoba dzwoniąca wcale nie musi wiedzieć o "ścieżce".

Wszystko sprowadza się do tego, czego potrzebuje rozmówca, jakoś myślę, że jesteś dzwoniącym dla tego kodu, więc nie ma sensu próbować odgadnąć, co jest potrzebne na tym poziomie transakcji.

+0

Tak, to rzeczywiście uprości to. W przypadku wyjątku po prostu utrzymuje ścieżkę i obsługuje ją tylko na górze. W rzeczywistości muszę usunąć wszystkie procedury obsługi wyjątków i wreszcie bloki, aby to działało, ale usunięcie kodu jest zawsze łatwe :-) –

+0

Btw: klasa nie jest tak czy inaczej wątkiem. –

5

Wystarczy uproszczenie (nieznacznie) obchodzenia wyjątek:

void Recursive(int x) 
{ 
    // maintain the recursion path information 
    path.Push(x); 

    try 
    { 
     // do some stuff and recursively call the method 
     Recursive(x + 6); 
    } 
    catch(RecursionException) 
    { 
     throw; 
    } 
    catch(Exception) 
    { 
     throw new RecursionException(path.ToString(), ex); 
    } 
    finally 
    { 
     // maintain the recursion path information 
     path.Pop() 
    } 
} 

Otrzymasz callstack informacji w za wyjątkiem jeśli to w jakikolwiek sposób użyteczne dla was, poza tym można napisać to jako urywek następnie wystarczy włożyć że gdzie potrzeba do ponownego użycia.

Istnieje również możliwość, która będzie powolny, ale powinno działać:

void DoStuff() 
{ 
    this.Recursive(1, this.RecursiveFunction1); 
    this.Recursive(2, this.RecursiveFunction2); 
} 

bool RecursiveFunction1(int x) 
{ 
    bool continueRecursing = false; 

    // do some stuff 
    return continueRecursing; 
} 

bool RecursiveFunction2(int y) 
{ 
    bool continueRecursing = false; 

    // do some other stuff here 
    return continueRecursing; 
} 

private void Recursive(int x, Func<int, bool> actionPerformer) 
{ 
    // maintain the recursion path information 
    path.Push(x); 

    try 
    { 
     // recursively call the method 
     if(actionPerformer(x)) 
     { 
      Recursive(x + 6, actionPerformer); 
     } 
    } 
    catch(RecursionException) 
    { 
     throw; 
    } 
    catch(Exception ex) 
    { 
     throw new RecursionException(path.ToString(), ex); 
    } 
    finally 
    { 
     // maintain the recursion path information 
     path.Pop(); 
    } 
} 
+0

Tak, oczywiście, to naprawia głupie "jest". Ale w rzeczywistości chciałbym uniknąć całego spróbujmy, co jest bolesne w tak wielu miejscach. –

+0

Dodałem wersję, która działa, przekazując delegat, który pobiera argument int i zwraca wartość logiczną. W ten sposób można przekazać metodę, która wykona rzeczywistą pracę, a następnie zwróci, czy kontynuować recursing. W tym momencie twoja rzeczywista metoda rekursji będzie jedynie ramą do podnoszenia ciężkich przedmiotów. Będzie wolniejszy niż poprzedni bit kodu. –

+0

Wszystkie metody rekurencyjne mają różne sygnatury. Gdybym musiał podzielić każdego z nich, po prostu stałoby się to jeszcze bardziej skomplikowane. –

4

Co wyszarpując obsługi catch z funkcji rekurencyjnej i po prostu pisać bez rekursji mniej manipulacji?

void StartRecursion(int x) 
{ 
    try 
    { 
     path.Clear(); 
     Recursive(x); 
    } 
    catch (Exception ex) 
    { 
     throw new RecursionException(path.ToString(), ex); 
    } 
} 

void Recursive(int x) 
{ 
    path.Push(x); 
    Recursive(x + 6); 
    path.Pop(); 
} 

void Main() 
{ 
    StartRecursion(100); 
} 
+0

ale jak uzyskać szczegółowe informacje o "ścieżce" do wyjątku? –

+0

Nie widzę sensu. Gdzie jest używane StartRecursion? Gdzie jest ścieżka.Pop? –

+0

Sprawdź zmiany. –

0
void Recursive(int x) 
{ 
    // maintain the recursion path information 
    path.Push(x); 

    try 
    { 
    // do some stuff and recursively call the method 
    Recursive(x + 6); 
    } 
    finally 
    { 
    // maintain the recursion path information 
    path.Pop() 
    } 
} 

void Recursive2(int x) 
{ 
    try 
    { 
    Recursive(x); 
    } 
    catch() 
    { 
     // Whatever 
    } 
} 

ten sposób można obsłużyć tylko jeden raz, jeśli wyjątek budzi Recursive2 uchwyty, tym rekurencji zostaje przerwana.

+0

, ale jak uzyskać szczegółowe informacje o "ścieżce" do wyjątku? –

4

Twój problem dotyczy obsługi wyjątków. Zawijanie wyjątku we własnym wyjątku jest generalnie złym pomysłem, ponieważ obciąża ono wywołującego twój kod, aby obsługiwał twój wyjątek. Osoba dzwoniąca, która podejrzewa, że ​​może, na przykład, wywołać wyjątek "nie można odnaleźć ścieżki", wywołując kod, nie może zawijać połączenia w try-catch, który przechwytuje wyjątek IOException. Muszą złapać twój wyjątek RecursionException, a następnie napisać kilka kodu, aby go przesłuchać, aby ustalić, jaki to był wyjątek. Są chwile, kiedy ten wzór jest uzasadniony, ale nie widzę, że jest to jeden z nich.

Chodzi o to, że w ogóle tutaj nie jest konieczne używanie wyjątków. Oto niektóre pożądane aspekty rozwiązania:

  • dzwoniący może złapać cokolwiek rodzaju wyjątku chcą
  • w debugowania, dzwoniący może określić informacje o tym co funkcja rekurencyjna robił, gdy wyjątek.

OK, super, jeśli takie są cele projektu, a następnie wdrożyć że:

class C 
{ 
    private Stack<int> path 

#if DEBUG

= new Stack<int>(); 

#else

= null; 

#endif

public Stack<int> Path { get { return path; } } 

    [Conditional("DEBUG")] private void Push(int x) { Path.Push(x); } 
    [Conditional("DEBUG")] private void Pop() { Path.Pop(); } 
    public int Recursive(int n) 
    { 
    Push(n); 
    int result = 1; 
    if (n > 1) 
    { 
     result = n * Recursive(n-1); 
     DoSomethingDangerous(n); 
    } 
    Pop(); 
    return result; 
    } 
} 

A teraz dzwoniący może sobie z tym poradzić:

C c = new C(); 
try 
{ 
    int x = c.Recursive(10); 
} 
catch(Exception ex) 
{ 

#if DEBUG

// do something with c.Path 

Widzisz, co tu robimy? Wykorzystujemy fakt, że wyjątek zatrzymuje algorytm rekurencyjny na swoich ścieżkach.Ostatnią rzeczą, jaką chcemy zrobić, to oczyścić ścieżkę, wpadając w końcu; my chce wyskakuje do wyrzucenia na wyjątku!

Sens?

+0

Tak, to ma sens. Ian Ringrose zasugerował już coś takiego i przyjąłem jego odpowiedź. Przy okazji, ścieżka jest zawsze dostępna, nie potrzebuję jej tylko dla wyjątku i nie jest to funkcja tylko do debugowania. Ale to nie ma znaczenia dla rozwiązania. Mimo wszystko dziekuję! –

+0

Korzystanie z warunkowego attrubu jest niedokładne, ale często potrzebowałem tego krótkiego czasu na debugowanie informacji o problemach klienta, trzeba o tym myśleć indywidualnie. –