Are there any good reasons why closures aren't immutable in C#?

≯℡__Kan透↙ 提交于 2019-12-31 16:46:44

问题


I've been going over and over this in my head, and I can't seem to come up with a good reason why C# closures are mutable. It just seems like a good way to get some unintended consequences if you aren't aware of exactly what's happening.

Maybe someone who is a little more knowledgeable can shed some light on why the designers of C# would allow state to change in a closure?

Example:

var foo = "hello";
Action bar = () => Console.WriteLine(foo);
bar();
foo = "goodbye";
bar();

This will print "hello" for the first call, but the outside state changes for the second call, printing "goodbye." The closure's state was updated to reflect the changes to the local variable.


回答1:


C# and JavaScript, as well as O'Caml and Haskell, and many other languages, have what is known as lexical closures. This means that inner functions can access the names of local variables in the enclosing functions, not just copies of the values. In languages with immutable symbols, of course, such as O'Caml or Haskell, closing over names is identical to closing over values, so the difference between the two types of closure disappears; these languages nevertheless have lexical closures just like C# and JavaScript.




回答2:


Not all closures behave the same. There are differences in semantics.

Note that the first idea presented matches C#'s behavior... your concept of closure semantics may not be the predominate concept.

As for reasons: I think the key here is ECMA, a standards group. Microsoft is just following their semantics in this case.




回答3:


This is actually a fantastic feature. This lets you have a closure that accesses something normally hidden, say, a private class variable, and let it manipulate it in a controlled way as a response to something like an event.

You can simulate what you want quite easily by creating a local copy of the variable, and using that.




回答4:


You have to also remember that in C# there is really no concept of immutable types. Because the whole objects in the .Net framework just don't get copied (you have to explicitly implement ICloneable, etc), this code would print "goodbye" even if the "pointer" foo was copied in the closure:

class Foo
{
    public string Text;
}    
var foo = new Foo();
foo.Text = "Hello";
Action bar = () => Console.WriteLine(foo.Text);
bar();
foo.Text = "goodbye";
bar();

So its questionable if in the current behaviour it is easier to get unintended consequences.




回答5:


When you create a closure, the compiler creates a type for you that has members for each captured variable. In your example the compiler would generate something like this:

[CompilerGenerated]
private sealed class <>c__DisplayClass1
{
    public string foo;

    public void <Main>b__0()
    {
        Console.WriteLine(this.foo);
    }
}

Your delegate is given a reference to this type so that it can use the captured variables later. Unfortunately, the local instance of foo is also changed to point here so any changes locally will affect the delegate as they use the same object.

As you can see the persistence of foo is handled by a public field rather than a property so there is not even an option of immutability here with the current implementation. I think what you want would have to be something like this:

var foo = "hello";
Action bar = [readonly foo]() => Console.WriteLine(foo);
bar();
foo = "goodbye";
bar();

Pardon the clumsy syntax but the idea is to denote that foo is captured in a readonly fashion which would then hint to the compiler to output this generated type:

[CompilerGenerated]
private sealed class <>c__DisplayClass1
{
    public readonly string foo;

    public <>c__DisplayClass1(string foo)
    {
        this.foo = foo;
    }

    public void <Main>b__0()
    {
        Console.WriteLine(this.foo);
    }
}

This would give you what you wanted in a certain fashion but would require updates to the compiler.




回答6:


In regards to why are closures mutable in C#, you have to ask, "Do you want simplicity (Java), or power with complexity (C#)?"

Mutable closures allow you to define once and reuse. Example:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ClosureTest
{
    class Program
    {   
        static void Main(string[] args)
        {
            string userFilter = "C";            
            IEnumerable<string> query = (from m in typeof(String).GetMethods()
                                         where m.Name.StartsWith(userFilter)
                                         select m.Name.ToString()).Distinct();

            while(userFilter.ToLower() != "q")
            {
                DiplayStringMethods(query, userFilter);
                userFilter = GetNewFilter();
            }
        }

        static void DiplayStringMethods(IEnumerable<string> methodNames, string userFilter)
        {
            Console.WriteLine("Here are all of the String methods starting with the letter \"{0}\":", userFilter);
            Console.WriteLine();

            foreach (string methodName in methodNames)
                Console.WriteLine("  * {0}", methodName);
        }

        static string GetNewFilter()
        {
            Console.WriteLine();
            Console.Write("Enter a new starting letter (type \"Q\" to quit): ");
            ConsoleKeyInfo cki = Console.ReadKey();
            Console.WriteLine();
            return cki.Key.ToString();
        }
    }
}

If you do not want to define once and reuse, because you are worried about unintended consequences, you can simply use a copy of the variable. Change the above code as follows:

        string userFilter = "C";
        string userFilter_copy = userFilter;
        IEnumerable<string> query = (from m in typeof(String).GetMethods()
                                     where m.Name.StartsWith(userFilter_copy)
                                     select m.Name.ToString()).Distinct();

Now the query will return the same result, regardless of what userFilter equals.

Jon Skeet has an excellent introduction to the differences between Java and C# closures.



来源:https://stackoverflow.com/questions/483944/are-there-any-good-reasons-why-closures-arent-immutable-in-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!