0

I recently created this extension method:

public static TR Calculate<TS, TR>(this TS item, Func<TS, TR> selector)
    => selector(item);

Then, I am able to refactor this ugly code, from:

int value = panel.Controls.Cast<Control>().Last().Location.Y
       + panel.Controls.Cast<Control>().Last().Size.Height;

to:

int value = panel.Controls.Cast<Control>().Last().Calculate(
    o => o.Location.Y + o.Size.Height);

I would like to know if there is a way to do this using built in .NET functions instead?

Maxime Recuerda
  • 476
  • 3
  • 14
  • 7
    What is an innate function? – mjwills Jun 13 '18 at 12:57
  • Did you try System.Linq? – BrunoMartinsPro Jun 13 '18 at 13:00
  • I guess what you want is to use `Select` to build a new type and then pick the first instead of getting a collection? that can be done using `Select(...).First()` – X39 Jun 13 '18 at 13:03
  • By innate I mean something already existing in .NET. Linq is used for Enumerable, I want the Select method for a single item, not a list / array / whatever. I wouldn't like to use IEnumerable.Select.ElementAt because it would execute Select for all previous items. – Maxime Recuerda Jun 13 '18 at 13:03
  • The input is a scalar (a single element of an array is a scalar) and the output is a scalar. I don't understand what you're trying to achieve here... Why not just `var x = collection[calc(param)]; var value = x.First + x.Second + x.Property.Value;` ? – Matthew Watson Jun 13 '18 at 13:04
  • Doesn't `IEnumerable` already have an extension method named `Single` that takes a `Func` and returns a single item? – Bradley Uffner Jun 13 '18 at 13:09
  • 1
    @BradleyUffner Yes, but that takes an `IEnumerable` rather than `T`. As I said above, the input and output is a scalar, so `IEnumerable` is irrelevant. – Matthew Watson Jun 13 '18 at 13:09
  • 1
    @MatthewWatson [MSDN Says](https://msdn.microsoft.com/en-us/library/bb535118(v=vs.110).aspx) it returns a single instance of `T`, not an `IEnumerable`. – Bradley Uffner Jun 13 '18 at 13:10
  • @BradleyUffner Oops sorry, what I should have said is that it *takes* an `Enumerable` rather than a `T`, so it won't work with the OP's code. (I've edited my original comment to fix it.) – Matthew Watson Jun 13 '18 at 13:14
  • @mjwills editted – Maxime Recuerda Jun 13 '18 at 13:14
  • @MatthewWatson Ahh, I see what you are trying to do now. You are correct, the exiting `Single` is not applicable here. – Bradley Uffner Jun 13 '18 at 13:14
  • 1
    `SingleSelect` is a weird name since it took a single thing and passed out a single thing. LINQ doesn't generally do that. I think `Calculate` or `Execute` would be a better name. I still don't like the pattern though. – mjwills Jun 13 '18 at 13:15
  • A whole extension method or this seems like overkill, why not just `int value = o.Location.Y + o.Size.Height;`? It's shorter, and uses existing constructs. – Bradley Uffner Jun 13 '18 at 13:18

3 Answers3

5

By "innate", I guess you mean you don't want to any new methods yourself?

Well, you can do something like this:

value = ((Func<InputType, OutputType>)(o => o.First + o.Second + o.Property.Value))(collection[calc(param)]);

Basically, casting the lambda to a delegate type and then calling the delegate.

Or maybe I have overcomplicated this. What you actually want might toto just not write collection[calc(param)] that many times. If that's the case, you can just:

var o = collection[calc(param)];
value = o.First + o.Second + o.Property.Value;

I don't see why you can't do it like that.

I really suggest you write a separate a method for this kind of transformation. It will be much more readable.

// name this properly so that it describes what you actually want.
public OutputType TransformCollectionItem(TInput o) {
    return o.First + o.Second + o.Property.Value;
} 
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • This is it, but I agree that this solution is not easy to read / understand. Sould I conclude I have to keep my current method? I actually could do it like that, but I do not like to create variables for a "one-line-use". – Maxime Recuerda Jun 13 '18 at 13:08
  • 2
    @MaximeR. Don't even make a `SingleSelect` for this. Just create a method specially for transforming the data. – Sweeper Jun 13 '18 at 13:10
5

I'm going to say that you shouldn't do that - I don't think it really makes the code more readable.

Given the original code:

int value = panel.Controls.Cast<Control>().Last().Location.Y
          + panel.Controls.Cast<Control>().Last().Size.Height;

Your extension method lets you do this:

int value = panel.Controls.Cast<Control>().Last().Calculate(
    o => o.Location.Y + o.Size.Height);

But there seems to be very little advantage over doing this instead:

var c = panel.Controls.Cast<Control>().Last();
int value = c.Location.Y + c.Size.Height;

The latter is actually shorter, and doesn't require the reader to know about an extension method.

Also, your extension method will appear in the tooltip for EVERYTHING...

Mind you, it does allow some interesting code such as

double negated = 1.0.Calculate(x => -x);
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • I added `where TS : class` to avoid your last point. Even if it will disable the uses for structs like Size, Color, ..., it'll be enough for now. – Maxime Recuerda Jun 13 '18 at 14:44
4

The operation you're describing here is usually called "apply", since it is simply applying a function to an argument as an extension method. You ask for a way to do it "innately"; I'm not quite sure what you mean. Function application is already innately built into the syntax of C#. To apply a function F to an argument a to get a result r in C# you do it like this:

r = F(a)

So in your case you would assign the lambda to a variable and then apply it, or, as the other answer notes, cast it to the appropriate function type and apply it.

If you want function application as an extension method that takes a lambda, you'll have to write it yourself.

What you are effectively calling out here is that "let expressions" are missing from C#. What I would really want to write here is:

int value = 
  let o = panel.Controls.Cast<Control>().Last() in 
  o.Location.Y + o.Size.Height;

Which many languages support, but C# only supports in query comprehensions.

See my answer to this question for more on "let expressions" in C# and why they would be useful, and how they are equivalent to your application function: DRY (Don't Repeat Yourself) and if assignements


Incidentally, if you name your method Select and make a double-application version called SelectMany, then you can use LINQ on... well, anything:

using System;
static class X
{
    public static R Select<A, R>(this A item, Func<A, R> selector) =>
      selector(item);
    public static R SelectMany<A, B, R>(this A item, Func<A, B> toB, Func<A, B, R> toR) =>
      toR(item, toB(item));
}
public class P
{
    public static void Main()
    {
        string myString = "hello";
        string result = from s in myString
                        from b in s + "goodbye"
                        select b.ToUpper() + " " + s;
        Console.WriteLine(result);
    }
}

That is a bizarrely baroque way to write a simple program, but if you are really bent on making extension methods that do things that are already in the language, you can do it!


Exercise: Implement the identity monad. You are well on your way!

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Wow, great answer. – mjwills Jun 13 '18 at 13:36
  • But will EF be able to transform those extensions into SQL when translating Linq? Or will it complain that they are not built-in (and therefore that it doesn't know how to translate them)? In my opinion that would be the number-one use case of creating a "Select" for a single item : To set a variable in a "Linq" manner, to avoid a statement body in a lambda expression. Hence resolving CS0834, aka "A lambda expression with a statement body cannot be converted to an expression tree" – jeancallisti Jun 29 '22 at 08:05