开发者

C# going nuts when I declare variables with the same name as the ones in a lambda

开发者 https://www.devze.com 2022-12-26 11:44 出处:网络
I have the following code (generates a quadratic function given the a, b, and c) Func<double, double, double, Func<double, double>> funcGenerator = (a, b, c) => f => f * f * a + b *

I have the following code (generates a quadratic function given the a, b, and c)

Func<double, double, double, Func<double, double>> funcGenerator = (a, b, c) => f => f * f * a + b * f + c;

Up until now, lovely.

But then, if i try to declare a variable named a, b, c or f, visual studio pops a "A local variable named 'f' could not be declared at this scope because it would give a different meaning to 'f' which is used in a child scope."

Basically, this fails, and I have no idea why, because a child scope doesn't even make any sense.

Func<double, double, double, Func<double, double>> funcGenerator =
    (a, b, c) => f => f * f * a + b * f + c;  
var f = 开发者_如何转开发3; // Fails  
var d = 3; // Fine

What's going on here?


I think what you're misunderstanding is that the order of declarations does not matter to the C# compiler with respect to scoping rules.

This:

Func<double, double, double, Func<double, double>> funcGenerator =
    (a, b, c) => f => f * f * a + b * f + c;  
var f = 3;
var d = 3;

Is exactly the same as this:

var f = 3;
Func<double, double, double, Func<double, double>> funcGenerator =
    (a, b, c) => f => f * f * a + b * f + c;  
var d = 3;

Scopes aren't order-sensitive. You have a local variable named f, and you are trying to declare another variable named f inside the lambda. This is illegal according to the C# spec.

Specifically, it would conflict with the ability of lambdas to do variable capturing. For example, this code is legal:

int x = 3;
Func<int> func = () => x + 1;

This is completely legal and executing func() will return 4. That's why you can't declare another variable x here inside the lambda - because the lambda actually needs to be capable of capturing the outer x.

Just change the name of one of the f variables.


It means what it says. You're not allowed to use the same variable name in the lambda scope and the scope containing the lambda. The child scope is the scope of the lambda.


It can be quite tricky to work out exactly which rule of C# you've violated. As a public service I've written this handy guide that explains the differences between some of the more easily-confused scoping rules:

http://ericlippert.com/tag/simple-names/

(Start at the bottom; these are in reverse-chronological order.)

Also, you seem a bit unclear on the concept of "scope" -- which is not surprising, since in most books the word is used to mean pretty much whatever the author wants. In C# we carefully define "scope" as "the region of program text in which a particular entity may be referred to by its unqualified name". So for example, in

namespace A 
{
  public class B 
  {
      private int c;
      protected int d;
      public void E(int f)
      {
         int g = f;
         Func<int, int> h = i => g * i;
      }
  }
  public class K : B { }
}

The scope of A is everywhere. The scopes of B and K are everywhere inside a declaration of A. The scope of c is everywhere inside B. The scopes of d and E are the contents of B, K and any class derived from B or K. The scopes of f and g and h are the body of E. The scope of i is the body of the lambda.

Notice that there is a difference between scope and accessibility domain. B, K and E are accessible everywhere, but are only in scope in particular locations.

Note also that h is in scope throughout the block. It is not legal to use h before its declaration, but it is in scope before its declaration.


The reason the lambda's parameters and variables from the enclosing scope are in the same "namespace" (in the loose name-binding sense, not the language-feature sense) is because lambdas can close over (refer to) variables from the enclosing scope. The lambda's parameters must be distinguishable from the variables that the lambda can see:

int x;
Action a = () => { x = 3; };

That's okay, it assigns 3 to the outer x.

int x;
Action<int> a = x => { x = 3; };

That's no good - which x are we assigning 3 to?

In your example the only difference is the order of declaration. This would produce an error

Action a = () => { x = 3; };
int x = 2;

The compiler would say you can't refer to x before it's declared. If you then make the lambda take a parameter with the same name, we arrive at roughly your example:

Action<int> a = x => { x = 3; };
int x;

If that worked, the compiler would basically be following a rule of the form: "try the various possible interpretations of the code, and whichever one isn't erroneous, assume it's the intended meaning". C# takes a slightly safer approach, and expects you to be specific and unambiguous rather than relying on such rules to "pick a winner".


A variable is declared inside the scope of a function. Since the lambda is compile/rewritten to some code inside that same function, it makes some kind of sense. I think the for loop where you define a variable as "first argument" of the for loop is the only exception.

Although I can imagine that it would be handy if you could reuse the variable names.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号