2

Please ignore whether a pattern like this is actually a good idea or not. The rationale was that I wanted to catch an error that was thrown, or a mismatch in the number of records inserted / updated / deleted. I didn't want to repeat this logic, and at the time this customisation felt like it was going to apply to no more than about four long "script" methods.

My first step was to use an anonymous function.

public void DoSqlAction(Func<bool> f, string task, string ctx, ref bool cont, List<ResultInfo> resultInfo) {
    if (cont) {
        bool ret = false;
        try {
            if (f.Invoke()) {
                resultInfo.Add(new ResultInfo(seq, task, "Success", ctx, true));
                cont = true;
            } else {
                resultInfo.Add(new ResultInfo(seq, task, "Fail", ctx, false));
                cont = false;
            }
        } catch (Exception ex) {
            resultInfo.Add(new ResultInfo(seq, task, "Error: " + ex.Message, ctx, false));
            cont = false;
        }
    }
}

if I try to use this:

 DoSqlAction(() => 1 == cx.Execute(someSql, anonymousTypeWithClassInstanceInside), "add item", refinfo.ToString(),ref cont, resultInfo);

anonymousTypeWithClassInstanceInside <-- source of the error

The error comes up:

Cannot use ref or out parameter 'abc' inside an anonymous method, lambda expression, or query expression

The solution is to get rid of the delegate Func<bool> f. I'm writing this entry (perhaps it should be a blog post?) because it felt that the compile-time error gets generated is a bit of a road block.

In this post, I discovered a link to Eric's article:

C# Cannot use ref or out parameter inside an anonymous method body

here

http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx

After seeing how foreach gets implemented, I was lead to think... hmmm... maybe I'm reaching for customisable syntactic sugar.

Is this possible now in C#4? Will it be possible in C#5? It makes me consider looking into http://nemerle.org at some point in the future, but I'd really like to stay within C#.

Community
  • 1
  • 1
sgtz
  • 8,849
  • 9
  • 51
  • 91
  • Considering a +1 simply for "...felt that the compile-time error [that] gets generated is a bit of a road block." I fail to see why you couldn't just return true or false here and determine continuation in a calling method that took as input a list of `SqlAction`s - is there additional code that actually uses cont? – Matt Mills Mar 05 '12 at 05:04
  • Why on earth wouldn't you just write it to return true or false for success or failure, instead of using the ref parameter? Or return a ResultInfo. Am I missing something? – mqp Mar 05 '12 at 05:09
  • @arootbeer: Agree. That's what I meant by "rip out Func f". It's just the way the code evoled in this case + that after implementing a pattern for reuse, I then have to go and alter everything. the "bool" idea is less flexible than a lamda... and what if we do end up finding have a cross-cutting pattern that we'd like to implement? After finding this, I'm inclined to always use some kind of OO pattern rather than a language based idea. – sgtz Mar 05 '12 at 05:13
  • ... and lamdas are powerful little devices. I don't like having something like this to trip over. Maybe it goes with the lamda teritory. – sgtz Mar 05 '12 at 05:16
  • I'm not going to comment whether what you've written is a good code. But it should compile fine. And since you don't have any `ref` or `out` in lambda expression, I think your problem is not in the code you posted. – svick Mar 05 '12 at 10:03
  • @svick - sounds like you've got an opinon re: quality ;-). Also, it absolutely will not compile in the scenario outlined. – sgtz Mar 05 '12 at 14:31
  • @sgtz, it absolutely will compile, I tried it. Like I said, there is no lambda with `ref` or `out` in your code, so there is no reason for that error message. – svick Mar 05 '12 at 18:24
  • @svick: okay, did you try the scenario where you are passing a class instance to the stuff inside the Func? This is when it stops compiling. From memory I had, *new { id = anObject.id, type = aCode.cde }* The problem to me is that this is quite expressive and works fine if you don't try to push that in to the Func... but it breaks once you take it a tiny bit further. It's almost like I need an option to say "resolve these assignments now, not later". – sgtz Mar 06 '12 at 05:26
  • @sgtz, no, but [it still compiles for me](http://ideone.com/5tFcI), even with that change (and I tried it in VS too). – svick Mar 06 '12 at 09:30

1 Answers1

0

In isolation, a way better way to write this code would be:

public ResultInfo DoSqlAction(Func<bool> f, string task, string ctx) {
    try {
        if (f.Invoke()) {
            return new ResultInfo(seq, task, "Success", ctx, true);
        } else {
            return new ResultInfo(seq, task, "Fail", ctx, false);
        }
    } catch (Exception ex) {
        return new ResultInfo(seq, task, "Error: " + ex.Message, ctx, false);
    }
}

And on the outside:

while (/* there's stuff to do */) {
    var result = DoSqlAction(/* the next stuff */);
    infos.Add(result);
    if (!result.Succeeded)
        break;
}

or the equivalent. This eliminates the odd side-effectful function, the ref parameter, etc. And it's shorter.

mqp
  • 70,359
  • 14
  • 95
  • 123
  • originally "ref bool cont, List resultInfo" were part of a *closure*. The function got wrapped locally. So the approach was more elegant, and made sense for a long script. The trouble comes when you try to pass a ref type (i.e. a class instance) to Func f. So this answer wont work. – sgtz Mar 05 '12 at 05:30
  • I still don't understand why being part of a closure makes this solution wrong; maybe if you presented more of the context it would become clear? – mqp Mar 05 '12 at 05:47
  • Correct on closure comment. My closure comment was supposed to be related to elegance (I should have stayed on topic). problem occurs in **c** -- DoSqlAction(method("string", new { a = 1, b = 2, **c = new ClassAbc(1,2,3,4)** })) – sgtz Mar 05 '12 at 05:54