2014-12-30 12 views
7

La domanda è in realtà molto semplice. Il seguente codice genera l'eccezione destra sotto di esso:Perché siamo autorizzati a usare const con i tipi di riferimento se possiamo assegnare solo null a loro?

class Foo 
{ 
    public const StringBuilder BarBuilder = new StringBuilder(); 
    public Foo(){ 

    } 
} 

Errore:

Foo.BarBuilder' is of type 'System.Text.StringBuilder'. A const field of a reference type other than string can only be initialized with null.

MSDN dice questo, che ho capito e ha senso da const prospettiva:

A constant expression is an expression that can be fully evaluated at compile time. Therefore, the only possible values for constants of reference types are string and a null reference.

Tuttavia, non vedo il motivo per cui o dove useremo la costante null. Quindi, perché in primo luogo un tipo di riferimento (diverso dalla stringa) può essere definito con const se può essere impostato solo su null e se è stata una decisione deliberata (che credo sia), quindi dove possiamo usare costante con null valori?

Aggiornamento:

Quando pensiamo a una risposta, per favore cerchiamo di pensare in modo diverso rispetto "Abbiamo questo modo perché non che ..." contesto.

+1

Stai chiedendo –

+0

'const' non è stato progettato per i tipi di riferimento – Fabio

+0

Il compilatore ti impedisce di usare const come questo, il messaggio dovrebbe _non_ essere interpretato come" puoi assegnare un tipo di riferimento const a null ", come puoi vedere non è utile . – kennyzx

risposta

5

Da MSDN

when the compiler encounters a constant identifier in C# source code (for example, months), it substitutes the literal value directly into the intermediate language (IL) code that it produces. Because there is no variable address associated with a constant at run time, const fields cannot be passed by reference and cannot appear as an l-value in an expression.

Poiché i tipi di riferimento (diversi nullo e stringhe che are special) devono essere costruiti in fase di esecuzione, quanto sopra non sarebbero possibili per i tipi di riferimento.

Per i tipi di riferimento, il più vicino si può ottenere è static readonly:

class Foo 
{ 
    // This is not a good idea to expose a public non-pure field 
    public static readonly StringBuilder BarBuilder = new StringBuilder(); 
    public Foo(){ 
    } 
} 

A differenza di sostituzione const (nel codice chiamante), static readonly crea una singola istanza condivisa del tipo di riferimento che ha subtle differences se le versioni di montaggio sono cambiato.

Anche se il riferimento non può (normally) essere riassegnato, esso non osta chiamando i metodi non puri sul StringBuilder (come Append ecc). Questo è diverso da consts, dove i tipi di valore e le stringhe sono immutabili (e probabilmente dovrebbe essere "eternal").

0

Come si afferma nella domanda, esiste un tipo di riferimento che può essere inserito in una stringa di riferimento const. Il compilatore mostra casi speciali e inserisce le stringhe nell'output compilato e consente loro di essere letti nel tipo di riferimento in fase di esecuzione.

Ovviamente questo pone la domanda: perché non avere stringhe essere gli unici tipi di riferimento che possono essere const, a condizione che siano comunque personalizzati? A tale scopo, posso solo ipotizzare che l'aggiunta di un caso speciale nel compilatore sia più semplice e meno problematica rispetto all'aggiunta di un caso speciale nella lingua . Dal punto di vista della lingua, una stringa è solo un tipo di riferimento, anche se il compilatore ha una gestione speciale per crearne delle istanze da stringhe letterali e risorse compilate.

+0

Grazie ad Avner, ma non penso che causerebbe così tanto problema poiché il compilatore può già distinguere la stringa da altri tipi di riferimento. La parte più difficile sarebbe questo trattamento speciale tra stringhe e altri tipi di riferimento, ma il compilatore lo gestisce già. Quello che sto chiedendo non dovrebbe essere così difficile. – Tarik

+0

Non è difficile, ma * rende la lingua più complessa *. È meglio mantenere il linguaggio semplice e complicato il compilatore –

+0

In realtà sta semplificando il linguaggio rendendolo più coerente. Il compilatore è già abbastanza complesso. – Tarik

0

Penso che tu stia chiedendo perché il tipo di riferimento con null lo consenta come costante.

Penso che tu abbia ragione che non ha molto senso ma è utile se hai progettato la tua libreria e se vuoi confrontarti con null ma vuoi dare un significato speciale (come confrontare con il valore della tua libreria solo piuttosto quindi direttamente nullo)

public class MyClass 
    { 
     public const MyClass MyClassNull = null; 
     public MyClass() 
     { 
     } 
    } 

utilizzo come questo.

object obj = GetMyClass(); 
if(obj == MyClass.MyClassNull) // This going to convert to actual null in MSIL. 
{  
} 
3

However, I don't see the reason why or where we would use null constant.

costanti Null sono utili come sentinel values.

Ad esempio, questo:

public class MyClass 
{ 
    private const Action AlreadyInvoked = null; 

    private Action _action; 

    public MyClass(Action action) { 
     _action = action; 
    } 

    public void SomeMethod() 
    { 
     _action(); 

     _action = AlreadyInvoked; 
    } 

    public void SomeOtherMethod() 
    { 
     if(action == AlreadyInvoked) 
     { 
      //... 
     } 
    } 
} 

è molto più espressiva di questo:

public class MyClass 
{ 
    //... 

    public void SomeMethod() 
    { 
     _action(); 

     _action = null; 
    } 

    public void SomeOtherMethod() 
    { 
     if(action == null) 
     { 
      //... 
     } 
    } 
} 

Il codice sorgente per la classe Lazy<T> mostra Microsoft ha utilizzato una strategia simile. Anche se hanno usato un delegato in sola lettura statica che non può mai essere invocato come un valore sentinella, avrebbero potuto appena usato una costante nulla, invece: "Perché mai usare` null`"

static readonly Func<T> ALREADY_INVOKED_SENTINEL = delegate 
{ 
    Contract.Assert(false, "ALREADY_INVOKED_SENTINEL should never be invoked."); 
    return default(T); 
};