0

I am creating a method to load default users into the identity database that comes with the MVC5 template. I have ended up with a compile error on the line (see full code below)

var user = CreateUser("email@email.email",true);

The exact error is Error 16 A local variable named 'user' cannot be declared in this scope because it would give a different meaning to 'user', which is already used in a 'child' scope to denote something else

I do not understand how this is happening, and what I find equally baffling is a soon as I comment out this line the next line cannot find variable user. If the compile refuses to let me use user due to a previous declaration, then how can it fail to find user after the duplicate declaration is removed.

I think this is related to closure/inline methods holding on to the name but am not sure. I also know I can fix this by just changing the variable name, I am more interested in the why.

Here is a simplified example of the method I am currently writing:

    internal static void CreateDefaultUsers(IdentityDb context)
{
    var defaultPassword = "admin";
    var userManager = new UserManager<User>(new UserStore<User>(context));
    var roleManager = new RoleManager<UserRole>(new RoleStore<UserRole>(context));

    Func<string, User> CreateUser = (string email) =>
    {
        User user = context.Users.SingleOrDefault(u => u.UserName == email);

        if (user == null)
        {
            user = new EziOrderUser { 
            FirstName = email.Substring(0, email.LastIndexOf("@")),
            Email = email, UserName = email, EmailConfirmed = true };

            userManager.Create(user, defaultPassword);

            userManager.AddToRole(user.Id, "User");
        }

        return user;
    };

    var user = CreateUser("email@email.email"); // <-- Error here
    userManager.AddToRole(user.Id, "Administrator"); // <-- Then here
}

(Have not checked that this sample compiles, it does illustrate the problem however)

Ryan Buddicom
  • 1,131
  • 15
  • 30
  • The answer is in the exception. It's not allowed, because you're using the same name twice. Just use a different name, or make your CreateUser a proper function. – Ray May 19 '15 at 06:50
  • 1
    @Ray: Way to answer by repeating the question... obviously there is more to it than that. The design isn't arbitrary. – Ed S. May 19 '15 at 06:52
  • @YuvalItzchakov Good job reopening the question just to correct the linked duplicate question… now you broke it. Note that we don’t reopen/reclose questions just to correct close reasons. You could have easily just linked it in the comment. – poke May 19 '15 at 06:54
  • I closed this question and deleted my answer. – Patrick Hofman May 19 '15 at 06:56
  • @pole If i find there is a better duplicate to help the OP get the proper answer, I'll re-open it and state so in the comments, just as I did, and let someone else close it, like patrick did. Nothing got "broken". Of course, I would of closed it with the other answer, but I didn't see it previously. – Yuval Itzchakov May 19 '15 at 06:57
  • 2
    Also [related question here](http://stackoverflow.com/q/29750618/2530848). Difference is, it conflicts with lambda parameter(which is as good as a local) – Sriram Sakthivel May 19 '15 at 06:58
  • durp, sorry for the duplicate! Couldn't find what I was looking for because I was missing that all important keyword "Scoping" ;) – Ryan Buddicom May 19 '15 at 07:08
  • @Ray and notice I specifically state one of those resolutions and explain I am more interested in the why :) – Ryan Buddicom May 19 '15 at 07:17

1 Answers1

1

Basically it's a feature made to make life easier for you. :-) If you could do this, you can get type clashes.

{
// scope 1

Func<string, User> CreateUser = (string email) =>
{
    // scope 2
    User user = context.Users.SingleOrDefault(u => u.UserName == email);

    // ...**1
    return user;
};

// **2
var user = CreateUser("email@email.email");
userManager.AddToRole(user.Id, "Administrator"); // <-- Then here

In your example, what is the type of User at **2 and **1? Say that CreateUser returns a string - that would infer that in scope 1 User can get both type string and type User. Since scope 2 is closed, you can argue that this is the correct one to use, but that will make it harder for developers to read the code. Enforcing that you can only use a name once per scope makes it easier.

Not all languages have this behavior, f.ex. C++ allows you to do these kinds of things. (which is probably the reason they did this in the first place :-)

By the ways, it's okay to re-use names, as long as they don't appear in the same scope. For example:

Func<string, User> CreateUser = (string email) =>
{
    User user = context.Users.SingleOrDefault(u => u.UserName == email);

    // ...
    return user;
};
{
    // New scope - user is not contained in a shared scope. This is OK.

    var user = CreateUser("email@email.email");
    userManager.AddToRole(user.Id, "Administrator"); 
}
atlaste
  • 30,418
  • 3
  • 57
  • 87
  • Yeah I had played around with scoping the second call and it did seem to work. I just find it strange that the inline method isn't equivalent to a normal method. ie Instead of Func<> I could just define `public User Create();` without changing the code at all. – Ryan Buddicom May 19 '15 at 07:08
  • 1
    @Hellfire you can also reference variables from the parent scope from within the `Func` if you want. That should answer that :-) – atlaste May 19 '15 at 07:19
  • Very true, I suppose the compiler might decide to move the declaration above the inline method or I could accidentally move it myself which would break existing code, refactoring it is ;) – Ryan Buddicom May 19 '15 at 10:56
  • @Hellfire in these kinds of cases there really is no benefit from using a `Func` over a simple method called `CreateUser`. If you want it to be part of the manager, you could even consider adding an extension method... I've even used partial classes to extend linq2sql code. It's all about the syntax and scope that makes the most sense really. – atlaste May 19 '15 at 11:07
  • Agreed on no benefit, I was mostly just trying to encapsulate the logic, I doubt I will use this function outside of this method ever. I am setting up roles the same way though and there's around 7 roles which just makes the method a lot cleaner, logic declaration followed by 7 calls to it with a string param. In this case I was pre-empting the need to add more than one default user and of course had to add the user to the admin role. I decided there should only be one admin account but possibly other default users and hit the problem of assigning a role... – Ryan Buddicom May 19 '15 at 11:16
  • ... The encasing method is called once at application start up and has no effect once it has run once so it just seemed odd to promote it to a function when the whole thing fits so nicely into one method. If I do break it anyone reading has to jump to the definition, process that and then pop back. Seems like a waste of time ;) – Ryan Buddicom May 19 '15 at 11:16
  • @Hellfire I would probably put that somewhere in a static constructor, and in this case get rid of the func completely. – atlaste May 19 '15 at 11:25