开发者

Is there a way to return only the function calls that do not throw exceptions with LINQ?

I'm getting tossed into an existing codebase, and par开发者_如何学Got of the job is to get the code gradually under test as I make updates. So it's a process of taking something old and ugly and making it nicer.

In this case, there is a code similar to this:

foreach (var thingamajigLocator in thingamajigLocators)
{
    Thingamajig thingamajig;
    try
    {
        thingamajig = thingamajigservice.GetThingamajig(thingamajigLocator);
    }
    catch
    {
        // exception logged further down, but is rethrown
        continue;
    }

    thingamajigCollection.Add(thingamajig);
}

It's ugly and in theory, if the exception is handled further down, it shouldn't be necessary to handle it here, but that's how the code is and currently, it's too much work to handle the service code.

I would love to do something like this:

thingamajigCollection = thingamajigLocators
                            .Select(tl => thingamajigservice.GetThingamajig(tl))
                            .Where( /* some condition that strips out the ones throwing exceptions */ );

Is this possible in any way? Any other suggestions? I can certainly leave the foreach with the try/catch, but it seems like it could be more elegant since I don't care what the actual exception is in this case. Which again, I know is horrible form, and will need to be addressed, but no time for it right now.


Actually, there is no difference. Func is just a delegate. So, you can make it really straightforward:

thingamajigCollection = thingamajigLocators
                        .Select(tl => 
                             {
                             try
                                 {
                                     return thingamajigservice.GetThingamajig(tl);
                                 }
                             catch(Exception)
                                 {
                                     return null;
                                 }
                             })
                          .Where(t1 => t1!=null);

As an option you can wrap GetThingamajig in new method to catch exceptions there.

NOTE: As hmemcpy said swallowing exceptions isn't the best way to go. So, you better try to redesign things.


How about a method as follows:

public IEnumerable<Thingamajig> GetThingamajigs()
{
    foreach (var thingamajigLocator in thingamajigLocators)
    {
        Thingamajig thingamajig;
        try
        {
            thingamajig = thingamajigservice.GetThingamajig(thingamajigLocator);
        }
        catch
        {
            // exception logged higher up
            // (how can this be? it just got swallowed right here)
            continue;
        }
        yield return thingamajig;

    }
}

The returned IEnumerable could then be used in a Linq statement, and the intent of the original code is not hidden in too much abstraction.


If you want to go a bit over-the-top to fairly cleanly ignore the excpetions, how 'bout something along the lines of:

thingamajigCollection = 
    thingamajigLocators
    .Select(tl => F.Try(()=> thingamajigservice.GetThingamajig(tl)))
    .Where(thing=>thing.HasValue)
    .Select(thing=>thing.Value)

With the following static helper class F:

public static Maybe<T> Try<T>(Func<T> f) {
    try {
        return Maybe<T>.FromValue(f());
    } catch (Exception e) {
        return Maybe<T>.FromException(e);
    }
}


public struct Maybe<T> {
    readonly T val;
    readonly Exception e;
    Maybe(T val, Exception e) { this.val = val; this.e = e; }

    public bool HasValue { get { return e == null; } }
    public T Value { 
        get {
            if (!HasValue) throw new InvalidOperationException("Attempted to get a value with an error", e); 
            else return val; 
        } 
    }
    public static Maybe<T> FromException(Exception e) { 
        return new Maybe<T>(default(T), e); 
    }
    public static Maybe<T> FromValue(T val) { 
        return new Maybe<T>(val, null); 
    }
}


I wonder exactly how is it that your exceptions are handled higher up, if you swallow and essentially ignore exceptions coming out of your service locators.

Instead of catching and swallowing the exception, I would implement a Null Object Pattern, returning a NullThingamajig from your service locators in case of an error. Then, you could simply use the following query to filter out the results:

var thingamajigs = thingamajigCollection.OfType<Thingamajig>();

EDIT

If you're unable to modify the service locators themselves, wrap them in a proxy object as well, that catches the exception and returns the null object for you:

public class ThingamajigLocatorProxy : IThingamajigLocator
{
    private readonly IThingamajigLocator locator;

    public ThingamajigLocatorProxy(IThingamajigLocator locator)
    {
        this.locator = locator;
    }

    public Thingamajig Locate()
    {
        try
        {
            return locator.Locate();
        }
        catch
        {
            // log exception
            return new NullThingamajig();
        }
    }
}

Then you could use the complete query below:

var thingamajig = thingamajigLocators
    .Select(locator => service.GetThingamajig(new ThingamajigLocatorProxy(locator)))
    .OfType<Thingamajig>();


You could have a static helper method which does what you want:

public static KeyValuePair<T, Exception> TryExecute<T>(Func<T> func) {
    try {
        return new KeyValuePair<T, Exception>(func(), null);
    } catch (Exception ex) {
        return new KeyValuePair<T, Exception>(default(T), ex);
    }
}

thingamajigCollection = thingamajigLocators
                            .Select(tl => TryExecute(() => thingamajigservice.GetThingamajig(tl)))
                            .Where(p => p.Value is null)
                            .Select(p => p.Key));

That should do the trick... and if you need, you can still examine the exception which has been thrown. And if you want to make it nicer, create a custom struct instead of KeyValuePair which has more suitable property names - but the concept would remain the same.


I suspect no. Any method could ultimately throw an exception.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜