Domanda

Ci ho pensato più e più volte nella mia testa e non riesco a trovare una buona ragione per cui le chiusure C# siano mutabili.Sembra semplicemente un buon modo per ottenere conseguenze indesiderate se non sei esattamente consapevole di cosa sta succedendo.

Forse qualcuno un po' più esperto può far luce sul motivo per cui i progettisti di C# consentirebbero il cambiamento dello stato in una chiusura?

Esempio:

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

Questo stamperà "Hello" per la prima chiamata, ma lo stato esterno cambia per la seconda chiamata, stampando "addio". Lo stato della chiusura è stato aggiornato per riflettere le modifiche alla variabile locale.

È stato utile?

Soluzione

C # e JavaScript, così come O'Caml e Haskell, e molte altre lingue, hanno ciò che è noto come chiusure lessicali . Ciò significa che le funzioni interne possono accedere ai nomi delle variabili locali nelle funzioni allegate, non solo alle copie dei valori . Nelle lingue con simboli immutabili, ovviamente, come O'Caml o Haskell, la chiusura dei nomi è identica alla chiusura dei valori, quindi la differenza tra i due tipi di chiusura scompare; queste lingue hanno comunque delle chiusure lessicali proprio come C # e JavaScript.

Altri suggerimenti

Non tutte le chiusure si comportano allo stesso modo. Ci sono differenze nella semantica .

Nota che la prima idea presentata corrisponde al comportamento di C # ... il tuo concetto di semantica di chiusura potrebbe non essere il concetto predominante.

Per motivi: penso che la chiave qui sia ECMA, un gruppo di standard. Microsoft sta solo seguendo la loro semantica in questo caso.

Questa è in realtà una funzionalità fantastica. Ciò ti consente di avere una chiusura che accede a qualcosa normalmente nascosto, per esempio, una variabile di classe privata, e ti consente di manipolarla in modo controllato come risposta a qualcosa come un evento.

Puoi simulare ciò che desideri abbastanza facilmente creando una copia locale della variabile e utilizzandola.

Devi anche ricordare che in C # non esiste davvero alcun concetto di tipi immutabili. Poiché tutti gli oggetti nel framework .Net non vengono copiati (è necessario implementare in modo esplicito ICloneable, ecc.), Questo codice stampa & Quot; arrivederci & Quot; anche se " pointer " foo è stato copiato nella chiusura:

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

Quindi è discutibile se nel comportamento attuale è più facile ottenere conseguenze indesiderate.

Quando crei una chiusura, il compilatore crea per te un tipo che dispone di membri per ogni variabile acquisita.Nel tuo esempio il compilatore genererebbe qualcosa del genere:

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

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

Al delegato viene fornito un riferimento a questo tipo in modo che possa utilizzare le variabili acquisite in seguito.Sfortunatamente, l'istanza locale di foo viene anche modificato per puntare qui in modo che qualsiasi modifica locale influisca sul delegato poiché utilizza lo stesso oggetto.

Come puoi vedere la persistenza di foo è gestito da un campo pubblico anziché da una proprietà, quindi non esiste nemmeno un'opzione di immutabilità qui con l'implementazione corrente.Penso che quello che vuoi dovrebbe essere qualcosa del genere:

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

Perdonate la sintassi goffa, ma l'idea è di denotarlo foo viene catturato in a readonly fashion che suggerirebbe quindi al compilatore di restituire questo tipo generato:

[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);
    }
}

Questo ti darebbe quello che volevi in ​​un certo modo ma richiederebbe aggiornamenti al compilatore.

Per quanto riguarda perché le chiusure sono mutabili in C #, devi chiedere, " Vuoi semplicità (Java) o potenza con complessità (C #)? " ;

Le chiusure mutabili ti consentono di definire una volta e riutilizzare. Esempio:

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();
        }
    }
}

Se non vuoi definire una volta e riutilizzare, perché sei preoccupato per conseguenze non intenzionali, puoi semplicemente usare una copia di della variabile. Modificare il codice sopra come segue:

        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();

Ora la query restituirà lo stesso risultato, indipendentemente da ciò che userFilter è uguale.

Jon Skeet ha un'eccellente introduzione alle le differenze tra le chiusure Java e C # .

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top