Partager via


A silly C# trick from the trenches

UPDATE: as Joe rightly points out in the comments (thanks Joe) when you try to pass a Func<T,V> to a method expecting Action<T> using lambda syntax it doesn't fail. Somehow the compiler must realize there is no match for Func<T,V> and look for a match for with Action<T> as a parameter. I don't know how I missed this. I suppose this just means that the s => {Func(s);;} syntax is even less useful than I had originally thought, you need a competing overload that does accept Func<T,V> which would take precedence... don't think that is very likely. So now it is a super silly trick. Sorry for the confusion everyone.

I’ve been playing around with a Fluent API for something.

Most of you will know that fluent API generally allow you to chain method calls together, so rather than writing this:

var pconfig = tconfig.ForProperty(t => t.Firstname);
pconfig.MaxLength(100);
pconfig.Nullable();

With a fluent style API you might do this instead:

tconfig.ForProperty(t => t.Firstname).MaxLength(100).Nullable();

All you need to make this the work is have MaxLength(..) and Nullable() return this.

So far so good.

But then I noticed something interesting.

I found that I occasionally need to be able to call these fluent methods in situations where I could care less about the return value. I.e. I have no intention of chaining another call.

Usually this sort of thing is benign.

If you don’t want to use the return from a method, well... don’t.

Things get a little more interesting inside a lambda though.

Let me explain how.

Imagine you have a method like the one below,

public Configuration<T> Configure<T>(
T t, Action<string> callback
);

Notice that one of the parameters is an Action<string> i.e. a method that takes one string parameter and returns void.

Now imagine you have a function like this:

public MyClass DoSomething(string value){
// Doesn’t really matter what this does with the
   // value so long as it returns non void.
return this;
}

That does what we really want to do in the callback AND additionally returns something.

Well as you might have guessed the ‘AND additionally’ can cause problems when writing lamdbas, because this:

var x = Configure(entity, (s) => DoSomething(s));

… won’t compile, the Lambda has the wrong type.

Hmm…

The solution is remarkably simple, make a multi-line lambda:

var x = Configure(entity, (s) => {DoSomething(s);;});

As you can see the second line does nothing.

Except, and here's the silly trick, changing the type of the lambda so it returns void… as required.

Next time you see ;; don’t assume it is a typo ;)

Comments

  • Anonymous
    July 24, 2009
    The comment has been removed
  • Anonymous
    July 24, 2009
    Interesting, I've never thought that was possible but it sure is  a neat way of doing any type of configuration!
  • Anonymous
    July 25, 2009
    Nice trick - I've done this the long way, but never seen it written like this.  I'd be a little concerned that it's not really easy to read, but it might just have a place.
  • Anonymous
    July 26, 2009
    @DarthObiwan (nice mix of names by the way... I didn't know Obiwan was a Sith spy!). Good sleuthing.@Jtenos, I agree about it being a little hard to read, grok, explain etc. If you use it in your code just put a hyperlink to this post ;)@Mikael its nice to hear that you think it is useful. Sometimes I see myself writing this kind of stuff and I really wonder...
  • Anonymous
    July 28, 2009
    Just copied some of your code to try for myself, and found that it actually does work for me without the special syntax:public Configuration Configure(Action<string> callback) { return null; }public MyClass DoSomething(string value) { return this; }// No compiler problemsvar y = Configure(s => DoSomething(s));// Compiler errorvar z = Configure(DoSomething);// Compiler errorvar k = Configure(delegate(string s) { return DoSomething(s); });// No compiler errorvar j = Configure(delegate(string s) { DoSomething(s); });No compiler errors or warnings on the full lambda.  No compiler error on the old-style anonymous method either, but I wouldn't expect one there.I'm on .NET 3.5 SP1 using VS2008.
  • Anonymous
    July 28, 2009
    @Joe,Well don't I feel like an idiot!Thanks for point out the error of my ways.I thought I had checked this out, but obviously not, I made a stupid assumption.looks like I have to eat some humble pie.-Alex
  • Anonymous
    July 28, 2009
    No problemo...Definitely a good discussion point.  It was kind of weird that a simple lambda works when passing in just the name of the method doesn't work.  I wonder if that kind of thing will be dealt with in the 4.0 compiler, since they're doing a lot of covariance stuff, and that's kind of a similar concept.