İç içe geçmiş deneme yakalama bloklarını önlemek için desen?


114

Her biri bir istisna ile başarısız olabilecek üç (veya daha fazla) hesaplama yöntemim olduğu bir durumu düşünün. Başarılı olan bir hesaplama bulana kadar her hesaplamayı denemek için aşağıdakileri yapıyorum:

double val;

try { val = calc1(); }
catch (Calc1Exception e1)
{ 
    try { val = calc2(); }
    catch (Calc2Exception e2)
    {
        try { val = calc3(); }
        catch (Calc3Exception e3)
        {
            throw new NoCalcsWorkedException();
        }
    }
}

Bunu daha güzel bir şekilde gerçekleştiren kabul edilmiş bir model var mı? Elbette, her bir hesaplamayı, başarısızlık durumunda null döndüren bir yardımcı yönteme sarabilirim ve ardından yalnızca ??operatörü kullanabilirim , ancak bunu daha genel bir şekilde yapmanın bir yolu var mı (yani, kullanmak istediğim her yöntem için bir yardımcı yöntem yazmak zorunda kalmadan) )? Herhangi bir yöntemi bir dene / yakala ile saran ve başarısızlık durumunda null döndüren jenerikleri kullanarak statik bir yöntem yazmayı düşündüm, ancak bunu nasıl yapacağımdan emin değilim. Herhangi bir fikir?


Hesaplamayla ilgili bazı ayrıntıları ekleyebilir misiniz?
James Johnson

2
Temelde bir PDE'yi çözmenin / yaklaştırmanın farklı yöntemleridir. Üçüncü taraf bir kitaplıktan geliyorlar, bu yüzden onları hata kodları veya boş döndürmek için değiştiremiyorum. Yapabileceğim en iyi şey, her birini ayrı ayrı bir yöntemle paketlemek.
jjoelson

Calc yöntemleri projenizin bir parçası mı (üçüncü taraf kitaplığı yerine)? Öyleyse, istisnaları atan mantığı çıkarabilir ve hangi calc yönteminin çağrılması gerektiğine karar vermek için bunu kullanabilirsiniz.
Chris

1
Ben Java rastlamak bunun için başka bir kullanım durum yoktur - Bir ayrıştırmak gerekiyor Stringbir karşı Datekullanıyor SimpleDateFormat.parseve ben bir istisna atar ne zaman bir sonrakine geçmeden, sırayla birkaç farklı biçimlerini denemek gerekir.
Miserable Variable

Yanıtlar:


125

Mümkün olduğunca, kontrol akışı veya istisnai olmayan durumlar için istisnalar kullanmayın.

Ancak sorunuzu doğrudan yanıtlamak için (tüm istisna türlerinin aynı olduğunu varsayarak):

Func<double>[] calcs = { calc1, calc2, calc3 };

foreach(var calc in calcs)
{
   try { return calc(); }
   catch (CalcException){  }
} 

throw new NoCalcsWorkedException();

15
Bu varsayar Calc1Exception, Calc2Exceptionve Calc3Exceptionaynı temel sınıfa paylaşır.
Wyzard

3
Üstelik, ortak bir imza atıyor - ki bu pek de uzak değil. Güzel cevap.
TomTom

1
Ayrıca, continuecatch bloğunu ve catch bloğunu ekledim , breakböylece bir hesaplama çalıştığında döngü biter (bu bit için
Lirik'e

6
+1 sadece "Kontrol akışı için istisnalar kullanmayın" diyor, ancak "Mümkün olduğunca" yerine "ASLA ASLA" kullanacaktım.
Bill K

1
@jjoelson: A breakaşağıdaki deyimi calc();içinde try(ve hiçbir continuehiç deyimi) biraz daha deyimsel olabilir.
Adam Robinson

38

Sadece "kutunun dışında" bir alternatif sunmak için, özyinelemeli bir işleve ne dersiniz?

//Calling Code
double result = DoCalc();

double DoCalc(int c = 1)
{
   try{
      switch(c){
         case 1: return Calc1();
         case 2: return Calc2();
         case 3: return Calc3();
         default: return CalcDefault();  //default should not be one of the Calcs - infinite loop
      }
   }
   catch{
      return DoCalc(++c);
   }
}

NOT: Hiçbir şekilde işi halletmenin en iyi yolu olduğunu söylemiyorum, sadece farklı bir şekilde


6
Bir kez bir dilde "Hata Durumunda Devam Ettirme" yi uygulamak zorunda kaldım ve oluşturduğum kod buna çok benziyordu.
Jacob Krall

4
Bir for döngüsü oluşturmak için lütfen bir switch deyimi kullanmayın.
Jeff Ferland

3
Döngü için switch ifadesine sahip olmak sürdürülemez
Mohamed Abed

1
Cevabımın en verimli kod olmadığını biliyorum, ancak yine de bu tür şeyler için dene / yakala bloklarını kullanmak zaten gitmek için en iyi yol değil. Maalesef OP bir 3. taraf kitaplığı kullanıyor ve başarıyı sağlamak için elinden gelenin en iyisini yapmak zorunda. İdeal olarak, girdi önce doğrulanabilir ve başarısız olmayacağından emin olmak için seçilen doğru hesaplama işlevi seçilebilir - tabii ki daha sonra güvenli olmak için hepsini bir dene / yakala'ya koyabilirsin;)
musefan

1
return DoCalc(c++)eşdeğerdir return DoCalc(c)- sonradan artırılan değer daha derine aktarılmaz. Çalışmasını sağlamak (ve daha fazla belirsizlik katmak) daha çok benzeyebilir return DoCalc((c++,c)).
Artur Czajka

37

Yuvayı aşağıdaki gibi bir yöntemle düzleştirebilirsiniz:

private double calcStuff()
{
  try { return calc1(); }
  catch (Calc1Exception e1)
  {
    // Continue on to the code below
  }

  try { return calc2(); }
  catch (Calc2Exception e1)
  {
    // Continue on to the code below
  }

  try { return calc3(); }
  catch (Calc3Exception e1)
  {
    // Continue on to the code below
  }

  throw new NoCalcsWorkedException();
}

Ancak gerçek tasarım sorununun, esasen aynı şeyi yapan (arayanın bakış açısından) ancak farklı, ilgisiz istisnalar ortaya çıkaran üç farklı yöntemin varlığından kaynaklandığından şüpheleniyorum .

Bu üç istisna üstleniyor olan ilgisiz. Eğer hepsinin ortak bir temel sınıfı varsa, Ani'nin önerdiği gibi tek bir catch bloğu olan bir döngü kullanmak daha iyi olacaktır.


1
+1: Bu, soruna en temiz, en saçma çözüm. Burada gördüğüm diğer çözümler sadece sevimli olmaya çalışıyor, IMO. OP'nin dediği gibi, API'yi yazmadı, bu yüzden atılan istisnalara bağlı kaldı.
Nate CK

19

İstisnalara dayalı olarak mantığı kontrol etmemeye çalışın; ayrıca istisnaların yalnızca istisnai durumlarda atılması gerektiğini unutmayın. Çoğu durumda hesaplamalar, dış kaynaklara erişmedikleri veya dizeleri ayrıştırmadıkları veya başka bir şey yapmadıkları sürece istisnalar atmamalıdır. Her neyse, en kötü durumda, istisna mantığını kapsüllemek ve kontrol akışınızı sürdürülebilir ve temiz hale getirmek için TryMethod stilini (TryParse () gibi) izleyin:

bool TryCalculate(out double paramOut)
{
  try
  {
    // do some calculations
    return true;
  }
  catch(Exception e)
  { 
     // do some handling
    return false;
  }

}

double calcOutput;
if(!TryCalc1(inputParam, out calcOutput))
  TryCalc2(inputParam, out calcOutput);

Try modelini kullanan ve iç içe yerleştirilmiş yerine yöntem listesini birleştiren başka bir varyasyon şu durumlarda:

internal delegate bool TryCalculation(out double output);

TryCalculation[] tryCalcs = { calc1, calc2, calc3 };

double calcOutput;
foreach (var tryCalc in tryCalcs.Where(tryCalc => tryCalc(out calcOutput)))
  break;

ve eğer foreach biraz karmaşıksa, bunu açıklığa kavuşturabilirsiniz:

        foreach (var tryCalc in tryCalcs)
        {
            if (tryCalc(out calcOutput)) break;
        }

Dürüst olmak gerekirse, bunun gereksiz soyutlamalara neden olduğunu düşünüyorum. Bu korkunç bir çözüm değil, ancak çoğu durumda bunu kullanmam.
user606723

İstisna türünü umursamıyorsanız ve sadece koşullu kodu ele almak istiyorsanız .. bu nedenle, başarılı olsun ya da olmasın, bunu geri dönüşlü koşullu bir yönteme dönüştürmek, soyutlama ve sürdürülebilirlik açısından kesinlikle daha iyidir, bu şekilde açık bir tanımlayıcı yöntemle istisna işleme dağınık sözdizimini gizlersiniz .. daha sonra kodunuz bunu normal bir koşullu yöntem olduğu gibi halleder.
Mohamed Abed

Puanları biliyorum ve bunlar geçerli. Bununla birlikte, bu tür bir soyutlamayı (dağınıklığı / karmaşıklığı gizlemek) hemen hemen her yerde kullanırken gülünç hale gelir ve bir yazılım parçasını ne olduğunu anlamak çok daha zor hale gelir. Dediğim gibi, bu korkunç bir çözüm değil, ama onu hafife almam.
user606723

9

Hesaplama işlevleriniz için bir temsilci listesi oluşturun ve ardından bunlar arasında geçiş yapmak için bir süre döngüsüne sahip olun:

List<Func<double>> calcMethods = new List<Func<double>>();

// Note: I haven't done this in a while, so I'm not sure if
// this is the correct syntax for Func delegates, but it should
// give you an idea of how to do this.
calcMethods.Add(new Func<double>(calc1));
calcMethods.Add(new Func<double>(calc2));
calcMethods.Add(new Func<double>(calc3));

double val;
for(CalcMethod calc in calcMethods)
{
    try
    {
        val = calc();
        // If you didn't catch an exception, then break out of the loop
        break;
    }
    catch(GenericCalcException e)
    {
        // Not sure what your exception would be, but catch it and continue
    }

}

return val; // are you returning the value?

Bu size bunun nasıl yapılacağına dair genel bir fikir vermelidir (yani bu kesin bir çözüm değildir).


1
Elbette normalde asla yakalamamanız gerektiği gerçeği dışında Exception. ;)
DeCaf

@DeCaf dediğim gibi: "size bunun nasıl yapılacağına dair genel bir fikir vermeli (yani kesin bir çözüm değil)." Böylece OP, uygun istisna ne olursa olsun yakalayabilir ... jeneri yakalamaya gerek yoktur Exception.
Kiril

Evet, üzgünüm, onu oraya götürme ihtiyacı hissettim.
DeCaf

1
@DeCaf, en iyi uygulamalara o kadar aşina olmayanlar için geçerli bir açıklama. Teşekkürler :)
Kiril

9

Bu, MONADS için bir işe benziyor! Özellikle, Belki monad. Burada anlatıldığı gibi Belki monad ile başlayın . Ardından bazı uzantı yöntemleri ekleyin. Bu uzatma yöntemlerini özellikle sizin tanımladığınız sorun için yazdım. Monad'ların güzel yanı, durumunuz için gereken tam uzatma yöntemlerini yazabilmenizdir.

public static Maybe<T> TryGet<T>(this Maybe<T> m, Func<T> getFunction)
{
    // If m has a value, just return m - we want to return the value
    // of the *first* successful TryGet.
    if (m.HasValue)
    {
        return m;
    }

    try
    {
        var value = getFunction();

        // We were able to successfully get a value. Wrap it in a Maybe
        // so that we can continue to chain.
        return value.ToMaybe();
    }
    catch
    {
        // We were unable to get a value. There's nothing else we can do.
        // Hopefully, another TryGet or ThrowIfNone will handle the None.
        return Maybe<T>.None;
    }
}

public static Maybe<T> ThrowIfNone<T>(
    this Maybe<T> m,
    Func<Exception> throwFunction)
{
    if (!m.HasValue)
    {
        // If m does not have a value by now, give up and throw.
        throw throwFunction();
    }

    // Otherwise, pass it on - someone else should unwrap the Maybe and
    // use its value.
    return m;
}

Bunu şu şekilde kullanın:

[Test]
public void ThrowIfNone_ThrowsTheSpecifiedException_GivenNoSuccessfulTryGet()
{
    Assert.That(() =>
        Maybe<double>.None
            .TryGet(() => { throw new Exception(); })
            .TryGet(() => { throw new Exception(); })
            .TryGet(() => { throw new Exception(); })
            .ThrowIfNone(() => new NoCalcsWorkedException())
            .Value,
        Throws.TypeOf<NoCalcsWorkedException>());
}

[Test]
public void Value_ReturnsTheValueOfTheFirstSuccessfulTryGet()
{
    Assert.That(
        Maybe<double>.None
            .TryGet(() => { throw new Exception(); })
            .TryGet(() => 0)
            .TryGet(() => 1)
            .ThrowIfNone(() => new NoCalcsWorkedException())
            .Value,
        Is.EqualTo(0));
}

Kendinizi bu tür hesaplamaları sık sık yaparken bulursanız, belki de monad, kodunuzun okunabilirliğini artırırken yazmanız gereken standart kod miktarını azaltmalıdır.


2
Bu çözümü seviyorum. Ancak, daha önce monadlara maruz kalmamış herkes için oldukça opaktır, bu da c # 'da deyimsel olmaktan çok uzak olduğu anlamına gelir. İş arkadaşlarımdan birinin, gelecekte bu aptalca kod parçasını değiştirmek için monadlar öğrenmesini istemem. Yine de bu ileride başvurmak için harika.
jjoelson

1
Mizah duygusu için +1, bu soruna mümkün olan en geniş ve ayrıntılı çözümü yazmak ve ardından "kodunuzun okunabilirliğini artırırken yazmanız gereken standart kod miktarını azaltacağını" söylemek için.
Nate CK

1
Hey, System.Linq'te gizlenen büyük miktarda koddan şikayet etmiyoruz ve bu monadları tüm gün mutlu bir şekilde kullanıyoruz. Sanırım @ fre0n sadece belki de alet çantanıza Belki monadını koymaya istekliyseniz, bu tür zincirleme değerlendirmelerin daha kolay bakılması ve akıl yürütmesi anlamına gelir. Yakalanması kolay birkaç uygulama var.
Sebastian Good

Sırf kullandığı Maybeiçin bunu monadik bir çözüm yapmaz; onun monadik özelliklerinin sıfırını kullanır Maybeve bu yüzden de kullanabilir null. Ayrıca, "monadicly" kullanılan bu olurdu ters arasında Maybe. Gerçek bir monadik çözüm, kendi durumu olarak ilk istisnai olmayan değeri koruyan, ancak normal zincirleme değerlendirme işe yaradığında bu aşırı olur.
Dax Fohl

7

Try yöntemi yaklaşımının başka bir versiyonu . Bu, her hesaplama için bir istisna türü olduğundan, yazılan istisnalara izin verir:

    public bool Try<T>(Func<double> func, out double d) where T : Exception
    {
      try
      {
        d = func();
        return true;
      }
      catch (T)
      {
        d = 0;
        return false;
      }
    }

    // usage:
    double d;
    if (!Try<Calc1Exception>(() = calc1(), out d) && 
        !Try<Calc2Exception>(() = calc2(), out d) && 
        !Try<Calc3Exception>(() = calc3(), out d))

      throw new NoCalcsWorkedException();
    }

Bunun &&yerine her koşul arasında kullanarak iç içe geçmiş iflerden kaçınabilirsiniz .
DeCaf

4

Perl yapabileceğiniz foo() or bar()yürütmek, hangi bar()eğer foo()başarısız olur. C # 'da bunu "başarısız olursa, sonra" oluşturmayı görmüyoruz, ancak bu amaçla kullanabileceğimiz bir operatör var: boş birleştirme operatörü?? yalnızca ilk bölüm boş ise devam .

Hesaplamalarınızın imzasını değiştirebilirseniz ve istisnalarını (önceki yazılarda gösterildiği gibi) sararsanız veya geri dönmek için yeniden yazarsanız null, kod zinciriniz giderek daha kısa ve okunması kolay hale gelir:

double? val = Calc1() ?? Calc2() ?? Calc3() ?? Calc4();
if(!val.HasValue) 
    throw new NoCalcsWorkedException();

Ben senin fonksiyonları, değer sonuçlar için aşağıdaki değiştirmeler kullanılan 40.40içinde val.

static double? Calc1() { return null; /* failed */}
static double? Calc2() { return null; /* failed */}
static double? Calc3() { return null; /* failed */}
static double? Calc4() { return 40.40; /* success! */}

Bu çözümün her zaman uygulanabilir olmayacağının farkındayım, ancak çok ilginç bir soru sormuştunuz ve inanıyorum ki, konu nispeten eski olsa da, düzeltmeyi yapabileceğiniz zaman bunun dikkate alınmaya değer bir model olduğuna inanıyorum.


1
Sadece "teşekkür ederim" demek istiyorum. Bahsettiğiniz şeyi uygulamaya çalıştım . Umarım doğru anladım.
AlexMelw

3

Hesaplama yöntemlerinin aynı parametresiz imzaya sahip olduğu göz önüne alındığında, bunları bir listeye kaydedebilir ve bu listeyi yineleyebilir ve yöntemleri çalıştırabilirsiniz. Muhtemelen, Func<double>"türünün sonucunu döndüren bir işlev" anlamını kullanmanız sizin için daha da iyi olacaktır double.

using System;
using System.Collections.Generic;

namespace ConsoleApplication1
{
  class CalculationException : Exception { }
  class Program
  {
    static double Calc1() { throw new CalculationException(); }
    static double Calc2() { throw new CalculationException(); }
    static double Calc3() { return 42.0; }

    static void Main(string[] args)
    {
      var methods = new List<Func<double>> {
        new Func<double>(Calc1),
        new Func<double>(Calc2),
        new Func<double>(Calc3)
    };

    double? result = null;
    foreach (var method in methods)
    {
      try {
        result = method();
        break;
      }
      catch (CalculationException ex) {
        // handle exception
      }
     }
     Console.WriteLine(result.Value);
   }
}

3

Bir Task / ContinueWith kullanabilir ve istisnayı kontrol edebilirsiniz. İşte güzelleştirmeye yardımcı olacak güzel bir uzatma yöntemi:

    static void Main() {
        var task = Task<double>.Factory.StartNew(Calc1)
            .OrIfException(Calc2)
            .OrIfException(Calc3)
            .OrIfException(Calc4);
        Console.WriteLine(task.Result); // shows "3" (the first one that passed)
    }

    static double Calc1() {
        throw new InvalidOperationException();
    }

    static double Calc2() {
        throw new InvalidOperationException();
    }

    static double Calc3() {
        return 3;
    }

    static double Calc4() {
        return 4;
    }
}

static class A {
    public static Task<T> OrIfException<T>(this Task<T> task, Func<T> nextOption) {
        return task.ContinueWith(t => t.Exception == null ? t.Result : nextOption(), TaskContinuationOptions.ExecuteSynchronously);
    }
}

1

Atılan istisnanın gerçek türü önemli değilse, sadece yazısız bir yakalama bloğu kullanabilirsiniz:

var setters = new[] { calc1, calc2, calc3 };
bool succeeded = false;
foreach(var s in setters)
{
    try
    {
            val = s();
            succeeded = true;
            break;
    }
    catch { /* continue */ }
}
if (!suceeded) throw new NoCalcsWorkedException();

Bu her zaman listedeki her işlevi çağırmaz mı? if(succeeded) { break; }Yakalama sonrası gibi bir şeye atmak isteyebilir (amaçlanmamıştır) .
bir CVn

1
using System;

namespace Utility
{
    /// <summary>
    /// A helper class for try-catch-related functionality
    /// </summary>
    public static class TryHelper
    {
        /// <summary>
        /// Runs each function in sequence until one throws no exceptions;
        /// if every provided function fails, the exception thrown by
        /// the final one is left unhandled
        /// </summary>
        public static void TryUntilSuccessful( params Action[] functions )
        {
            Exception exception = null;

            foreach( Action function in functions )
            {
                try
                {
                    function();
                    return;
                }
                catch( Exception e )
                {
                    exception   = e;
                }
            }

            throw exception;
        }
    }
}

Ve böyle kullanın:

using Utility;

...

TryHelper.TryUntilSuccessful(
    () =>
    {
        /* some code */
    },
    () =>
    {
        /* more code */
    },
    calc1,
    calc2,
    calc3,
    () =>
    {
        throw NotImplementedException();
    },
    ...
);

1

Görünüşe göre OP'nin amacı, sorununu çözmek ve o anda mücadele ettiği mevcut sorunu çözmek için iyi bir model bulmaktı.

OP: "Her bir hesaplamayı, başarısızlık durumunda null döndüren bir yardımcı yönteme sığdırabilirim ve ardından yalnızca ??operatörü kullanabilirim , ancak bunu daha genel bir şekilde yapmanın bir yolu var mı (yani, istediğim her yöntem için yardımcı bir yöntem yazmak zorunda kalmadan herhangi bir yöntemi bir dene / yakala ile saran ve başarısızlık durumunda boş döndüren jenerikler kullanarak statik bir yöntem yazmayı düşündüm, ancak bunu nasıl yapacağımdan emin değilim. Herhangi bir fikir? "

İç içe geçmiş deneme yakalama bloklarını engelleyen birçok iyi desen gördümBu beslemede yayınlanan, , ancak yukarıda belirtilen soruna bir çözüm bulamayan . İşte çözüm:

OP'nin yukarıda bahsettiği gibi, başarısız olduğunda geri dönennull bir sarmalayıcı nesne yapmak istedi . Ben buna bir kapsül ( İstisna-güvenli bölme ) derdim .

public static void Run()
{
    // The general case
    // var safePod1 = SafePod.CreateForValueTypeResult(() => CalcX(5, "abc", obj));
    // var safePod2 = SafePod.CreateForValueTypeResult(() => CalcY("abc", obj));
    // var safePod3 = SafePod.CreateForValueTypeResult(() => CalcZ());

    // If you have parameterless functions/methods, you could simplify it to:
    var safePod1 = SafePod.CreateForValueTypeResult(Calc1);
    var safePod2 = SafePod.CreateForValueTypeResult(Calc2);
    var safePod3 = SafePod.CreateForValueTypeResult(Calc3);

    var w = safePod1() ??
            safePod2() ??
            safePod3() ??
            throw new NoCalcsWorkedException(); // I've tested it on C# 7.2

    Console.Out.WriteLine($"result = {w}"); // w = 2.000001
}

private static double Calc1() => throw new Exception("Intentionally thrown exception");
private static double Calc2() => 2.000001;
private static double Calc3() => 3.000001;

Ancak , CalcN () işlevleri / yöntemleri tarafından döndürülen Referans Türü sonucu için güvenli bir bölme oluşturmak isterseniz ne olur?

public static void Run()
{
    var safePod1 = SafePod.CreateForReferenceTypeResult(Calc1);
    var safePod2 = SafePod.CreateForReferenceTypeResult(Calc2);
    var safePod3 = SafePod.CreateForReferenceTypeResult(Calc3);

    User w = safePod1() ?? safePod2() ?? safePod3();

    if (w == null) throw new NoCalcsWorkedException();

    Console.Out.WriteLine($"The user object is {{{w}}}"); // The user object is {Name: Mike}
}

private static User Calc1() => throw new Exception("Intentionally thrown exception");
private static User Calc2() => new User { Name = "Mike" };
private static User Calc3() => new User { Name = "Alex" };

class User
{
    public string Name { get; set; }
    public override string ToString() => $"{nameof(Name)}: {Name}";
}

Bu nedenle, "kullanmak istediğiniz her yöntem için bir yardımcı yöntem yazmaya" gerek olmadığını fark edebilirsiniz .

Bölmelerin iki tip (için ValueTypeResults ve ReferenceTypeResults) olan yeterli .


İşte kodu SafePod. Yine de bir konteyner değil. Bunun yerine, hem s hem de s için istisna korumalı bir temsilci sarmalayıcısı oluşturur .ValueTypeResultReferenceTypeResult

public static class SafePod
{
    public static Func<TResult?> CreateForValueTypeResult<TResult>(Func<TResult> jobUnit) where TResult : struct
    {
        Func<TResult?> wrapperFunc = () =>
        {
            try { return jobUnit.Invoke(); } catch { return null; }
        };

        return wrapperFunc;
    }

    public static Func<TResult> CreateForReferenceTypeResult<TResult>(Func<TResult> jobUnit) where TResult : class
    {
        Func<TResult> wrapperFunc = () =>
        {
            try { return jobUnit.Invoke(); } catch { return null; }
        };

        return wrapperFunc;
    }
}

Birinci sınıf vatandaş varlıkların ??gücüyle birlikte sıfır birleştirme operatöründen bu şekilde yararlanabilirsiniz .delegate


0

Her bir hesaplamayı tamamlama konusunda haklısınız, ancak söyleme-sorma ilkesine göre sarmalamanız gerekir.

double calc3WithConvertedException(){
    try { val = calc3(); }
    catch (Calc3Exception e3)
    {
        throw new NoCalcsWorkedException();
    }
}

double calc2DefaultingToCalc3WithConvertedException(){
    try { val = calc2(); }
    catch (Calc2Exception e2)
    {
        //defaulting to simpler method
        return calc3WithConvertedException();
    }
}


double calc1DefaultingToCalc2(){
    try { val = calc2(); }
    catch (Calc1Exception e1)
    {
        //defaulting to simpler method
        return calc2defaultingToCalc3WithConvertedException();
    }
}

İşlemler basittir ve davranışlarını bağımsız olarak değiştirebilir. Ve neden varsayılan oldukları önemli değil. Bir kanıt olarak calc1DefaultingToCalc2'yi şu şekilde uygulayabilirsiniz:

double calc1DefaultingToCalc2(){
    try { 
        val = calc2(); 
        if(specialValue(val)){
            val = calc2DefaultingToCalc3WithConvertedException()
        }
    }
    catch (Calc1Exception e1)
    {
        //defaulting to simpler method
        return calc2defaultingToCalc3WithConvertedException();
    }
}

-1

Hesaplamalarınız, döndürülecek hesaplamanın kendisinden daha geçerli bilgilere sahip gibi görünüyor. Belki de kendi istisna işlemlerini yapmaları ve hata bilgileri, değer bilgileri vb. İçeren bir "sonuçlar" sınıfı döndürmeleri daha mantıklı olabilir. AsyncResult sınıfının zaman uyumsuz modeli izleyerek yaptığı gibi düşünün. Daha sonra hesaplamanın gerçek sonucunu değerlendirebilirsiniz. Bunu, bir hesaplama başarısız olursa, bu sanki başarılı gibi bilgi amaçlı olarak düşünerek mantıklı hale getirebilirsiniz. Bu nedenle, bir istisna, bir "hata" değil, bir bilgi parçasıdır.

internal class SomeCalculationResult 
{ 
     internal double? Result { get; private set; } 
     internal Exception Exception { get; private set; }
}

...

SomeCalculationResult calcResult = Calc1();
if (!calcResult.Result.HasValue) calcResult = Calc2();
if (!calcResult.Result.HasValue) calcResult = Calc3();
if (!calcResult.Result.HasValue) throw new NoCalcsWorkedException();

// do work with calcResult.Result.Value

...

Elbette, bu hesaplamaları yapmak için kullandığınız genel mimari hakkında daha çok merak ediyorum.


Bu tamam - hesaplamaları tamamladığında OP'nin önerdiği gibi. while (!calcResult.HasValue) nextCalcResult()Calc1, Calc2, Calc3 gibi bir liste yerine sadece bir şey tercih ederim
Kirk Broadhurst

-3

Yaptığınız eylemleri izlemeye ne dersiniz ...

double val;
string track = string.Empty;

try 
{ 
  track = "Calc1";
  val = calc1(); 

  track = "Calc2";
  val = calc2(); 

  track = "Calc3";
  val = calc3(); 
}
catch (Exception e3)
{
   throw new NoCalcsWorkedException( track );
}

4
Bu nasıl yardımcı olur? calc1 () başarısız olursa cals2 asla çalıştırılmayacaktır!
DeCaf

bu sorunu çözmez. Sadece calc2 başarısız olursa calc1'i çalıştır, sadece calc1 && calc2 başarısız olursa calc3'ü çalıştır.
Jason

+1 orn. Bu benim işim. Sadece bir yakalama kodunu , bana gönderilen mesajı ( trackbu durumda) kodlamalıyım ve kodumdaki hangi segmentin bloğun başarısız olmasına neden olduğunu tam olarak biliyorum . Belki de DeCaf gibi üyelere trackmesajın, kodunuzda hata ayıklamanızı sağlayan özel hata işleme rutininize gönderildiğini anlatmanız gerekir . Mantığınızı anlamamış gibi görünüyor.
jp2code

Evet, @DeCaf doğru, kod segmentim jjoelson'un istediği bir sonraki işlevi yürütmeye devam etmiyor, çünkü benim çözümüm uygulanabilir değil
Orn Kristjansson
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.