drumsta
drumsta

Reputation: 1384

How to skip the function with lambda code inside?

Consider the fragment below:

    [DebuggerStepThrough]
    private A GetA(string b)
    {
        return this.aCollection.FirstOrDefault(a => a.b == b);
    }

If I use F11 debugger doesn't skip the function instead it stops at a.b == b.

Is there any way to jump over this function rather than using F10?

Upvotes: 8

Views: 2114

Answers (3)

BrainSlugs83
BrainSlugs83

Reputation: 6412

This bothered me for a long time, today I figured out a way to do it using Expression trees in .NET 4.0.

Consider the following code:

private class Borked
{
    public object X 
    {
        [DebuggerStepThrough]
        get { throw new NotImplementedException(); }
    }
}

private void SomeMethod()
{
    var bad = new Borked();
    object obj = bad.TryGet(o => o.X);
}

Now -- I'm able to call this code without missing a beat -- the only place the debugger would try to stop is in Borked's broken getter property -- so that's why I added the DebuggerStepThrough attribute there.

Instead of taking in a lambda, I take in an expression tree (which uses the same syntax!!), and I compile and run it at runtime -- it's a little more work (barely) than using a regular lambda, and it won't work for everything, but for simple Linq queries and such, it works great.

All the magic happens in the following method -- which, again, originally used to accept a regular Func<> argument -- but that paused the debugger when an exception was thrown (despite the step-through attribute), so now I do it with an Expression<Func<>> like so:

[DebuggerStepThrough]
public static T TryGet<OT, T>(this OT obj, params Expression<Func<OT, T>>[] getters)
{
    T ret = default(T);

    if (getters != null)
    {
        foreach (var getter in getters)
        {
            try
            {
                if (getter != null)
                {
                    var getter2 = (Func<OT, T>)getter.Compile();
                    ret = getter2(obj);
                    break;
                }
            }
            catch
            { /* try next getter or return default */ }
        }
    }

    return ret;
}

That's right -- you just call .Compile() and cast the return value to a regular Func<> that you can immediately invoke!! -- How easy was that?!

In my implementation, I let the user pass in multiple parameters so that they have a way to get a fallback value (and that that fallback value is only created/evaluated IF required).

Also, I'm not sure if the reason the Debug event is being suppressed is because of VisualStudio's "Just my Code", or because it's being run inline in this method..., but either way it freakin' works, dang it!!

Now, I'm guessing that calling that .Compile method on the expression at run-time isn't super fast, but, in practice, it doesn't seem to be adding any performance penalty for me in my queries. So, I'm pretty excited (please excuse the multiple/redundant bangs and interobangs, etc.)

Upvotes: 0

Steven
Steven

Reputation: 172676

IMO this is a bug in the C# compiler. The compiler should also place those attributes on the anonymous methods. The workaround is to fallback to doing the work manually that the C# compiler does for you:

[DebuggerStepThrough]
private A GetA(string b)
{
    var helper = new Helper { B = b };

    return this.aCollection.FirstOrDefault(helper.AreBsEqual);
}

private class Helper
{
    public string B;

    [DebuggerStepThrough]
    public bool AreBsEqual(A a) { return a.b == this.B; }
}

But of course this is nasty and quite unreadable. That's why the C# compiler should have done this. But the difficult question for the C# team is of course: which of the attributes that you place on a method must be copied to internal anonymous methods and which should not?

Upvotes: 4

Tim Carter
Tim Carter

Reputation: 600

I can see why it happens but don't have a way to get around it. Perhaps someone can build on this. The lambda expression gets compiled into an Anonymous Method.

I see: Program.GetA.AnonymousMethod__0(Test a)

Just like if you called another method in the method you've shown, pressing F11 would go into that method. eg/

[DebuggerStepThrough]
static A GetA<A>(IList<A> aCollection, string b) where A : Test
{
    DoNoOp();
    return aCollection.FirstOrDefault(a => a.b == b);
}

static void DoNoOp()
{
    // noop
    Console.WriteLine("got here");
}

Upvotes: 2

Related Questions