简体   繁体   中英

What does the first arrow operator in this Func<T, TReturn> mean?

Given this example code:

enum op
{ 
  add, 
  remove
}

Func<op, int> combo(string head, double tail) => 
  (op op) => 
  op == op.add ? Int32.Parse(head) + Convert.ToInt32(tail) : Int32.Parse(head) - Convert.ToInt32(tail);

Console.WriteLine(combo("1", 2.5)(op.remove));

Which returns:

-1

What does the first arrow operator mean?
By specification it does not look like an expression body or a lambda operator.
Is there any reference in the C# language specification about this usage?

What does the first arrow operator mean?

In newer versions of C#, you are allowed to write:

int M(int x) 
{
  return x + x;
}

as the shorter and clearer:

int M(int x) => x + x;

It is nothing more than a "syntactic sugar" that lets you write a simple method in a shorter and more direct way.

it does not look like an expression body or a lambda operator.

The left => indicates an expression body. The right => indicates a lambda. So it is somewhat confusing in your example because the thing being returned is a lambda, which also uses => . But do not let that distract you:

Func<int, int> M() => x => x + 1;

is just a short way of writing

Func<int, int> M() 
{
  return x => x + 1;
}

Which in turn is just a short way or writing

static int Anonymous(int x)
{
  return x + 1;
}
Func<int, int> M() 
{
  return Anonymous;
}

If you find code with multiple => operators confusing you can always remove them by desugaring them into the more explicit form. It's just a short form that some people find easier to read.


Can you please write

static Func<op, int> combo(string head, double tail) =>
  (op op) => ...; 

with just one => operator.

Sure. Here are two equivalent ways to write that code.

// Block body, lambda return.
static Func<op, int> combo(string head, double tail)
{
  return (op op) => ...; 
}

// Block body, local function return:
static Func<op, int> combo(string head, double tail)
{
  op MyFunction(op)
  {
     return ...;
  }
  return MyFunction;
}

Sorry, I was not being explicit. What I meant was can you please extract the lambda into its own standalone function, like what one would do in the olden days.

Sure; that is a little more complicated. We will do it in a series of small steps:

We start with this:

Func<op, int> combo(string head, double tail) => 
  (op op) => 
    op == op.add ? Int32.Parse(head) + Convert.ToInt32(tail) : 
    Int32.Parse(head) - Convert.ToInt32(tail);

Desugar the outer function:

Func<op, int> combo(string head, double tail)
{
  return (op op) => 
    op == op.add ? Int32.Parse(head) + Convert.ToInt32(tail) : 
    Int32.Parse(head) - Convert.ToInt32(tail);
}

Turn the inner function into a helper:

static int Helper(string head, double tail, op op)
{
    return op == op.add ? Int32.Parse(head) + Convert.ToInt32(tail) : 
    Int32.Parse(head) - Convert.ToInt32(tail);
}

Func<op, int> combo(string head, double tail)
{
  return (op op) => Helper(head, tail, op);
}

Now move the helper method to a class:

private class Closure
{
  public int Helper(string head, double tail, op op)
  {
    return op == op.add ? Int32.Parse(head) + Convert.ToInt32(tail) : 
    Int32.Parse(head) - Convert.ToInt32(tail);
  }
}

Func<op, int> combo(string head, double tail)
{
  Closure c = new Closure();
  return (op op) => c.Helper(head, tail, op);
}

Now make head and tail members of closure:

private class Closure
{
  public string head;
  public double tail;
  public int Helper(op op)
  {
    return op == op.add ? Int32.Parse(head) + Convert.ToInt32(tail) : 
    Int32.Parse(head) - Convert.ToInt32(tail);
  }
}

Func<op, int> combo(string head, double tail)
{
  Closure c = new Closure();
  c.head = head;
  c.tail = tail;
  return (op op) => c.Helper(op);
}

And now we can desugar the lambda:

private class Closure
{
  public string head;
  public double tail;
  public int Helper(op op)
  {
    return op == op.add ? Int32.Parse(head) + Convert.ToInt32(tail) : 
    Int32.Parse(head) - Convert.ToInt32(tail);
  }
}

Func<op, int> combo(string head, double tail)
{
  Closure c = new Closure();
  c.head = head;
  c.tail = tail;
  return c.Helper;
}

And we're done.

If you decompile your code using ILDASM or sharplab.io or some such tool you will discover that this is exactly what the compiler generates for your code, except that it generates weird, illegal names for the helpers to ensure that you never accidentally call one of them by mistake.

Function that returns function, maybe easier to understand without lambda, written in "old" way, just for explanation, think that params closure is not ideal:

        string head;
        double tail;
        private int innerFunc(op op )
        {
            return op == op.add ? Int32.Parse(head) + Convert.ToInt32(tail) : Int32.Parse(head) - Convert.ToInt32(tail);
        }

        Func<op, int> comboWihoutLambda (string headprm, double tailprm)
        {
            head = headprm;
            tail = tailprm;
            return innerFunc;

        }

Think that => is the inversed logic Material implication used to define lambda expressions in C#.

It means that the result of the left operand is obtained by evaluating the right operand, like an equality.

So evaluating the left operand implies to evaluate the right operand.

So it is more like an equivalence <=> but since it is made to use the left operand in expression, the operator is => : writting left operand in an expression implies to use the right operand in place.

I say inversed because you can mathematically read: right operand implies left operand .

It is not equality operator, reserved for assignments, because there is a little difference.

So here, calling combo (left operand) "replace" it by its definition (right operand).

And with the code you provided there is an imbrication between 2 lambdas expressions...

So we pass "double" parameters like a "bidimentional jagged array" because combo returns a Func<> .

So the first () is the combo parameters and the second () is the returned Func parameters.

Explained in a vulgarized and dubious way but I hope this will help you to understand.

Lambda Expressions in C#

Anatomy of the Lambda Expression

C# - Func

Lambda expressions (C# Programming Guide)

The code of your question can be written as:

Console.WriteLine(combo("1", 2.5, op.remove));

int combo(string head, double tail, op op)
{
  return op == op.add ? Int32.Parse(head) + Convert.ToInt32(tail) 
                      : Int32.Parse(head) - Convert.ToInt32(tail);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM