Perché la parola chiave 'out' è usata in due contesti apparentemente disparati?

11

In C #, la parola chiave out può essere utilizzata in due modi diversi.

  1. Come modificatore di parametro in cui un argomento è passato per riferimento

    class OutExample
    {
        static void Method(out int i)
        {
            i = 44;
        }
        static void Main()
        {
            int value;
            Method(out value);
            // value is now 44
        }
    }
    
  2. Come modificatore del parametro di tipo per specificare covarianza .

    // Covariant interface. 
    interface ICovariant<out R> { }
    
    // Extending covariant interface. 
    interface IExtCovariant<out R> : ICovariant<R> { }
    
    // Implementing covariant interface. 
    class Sample<R> : ICovariant<R> { }
    
    class Program
    {
        static void Test()
        {
            ICovariant<Object> iobj = new Sample<Object>();
            ICovariant<String> istr = new Sample<String>();
    
            // You can assign istr to iobj because 
            // the ICovariant interface is covariant.
            iobj = istr;
        }
    }
    

La mia domanda è: perché?

Per un principiante, la connessione tra i due non sembra intuitiva . L'uso con i generici non sembra avere nulla a che fare con il passaggio per riferimento.

In primo luogo ho appreso cosa out era in relazione al passaggio degli argomenti per riferimento, e questo ha ostacolato la mia comprensione dell'uso della definizione della covarianza con i generici.

Esiste una connessione tra questi usi che mi manca?

    
posta Rowan Freeman 10.02.2015 - 05:24
fonte

2 risposte

19

C'è una connessione, tuttavia è un po 'allentata. In C # le parole chiave'in'e'out' come suggeriscono il loro nome rappresentano input e output. Questo è molto chiaro nel caso dei parametri di output, ma meno pulito di quello che deve fare con i parametri del template.

Diamo un'occhiata al principio di sostituzione di Liskov :

...

Liskov's principle imposes some standard requirements on signatures which have been adopted in newer object-oriented programming languages (usually at the level of classes rather than types; see nominal vs. structural subtyping for the distinction):

  • Contravariance of method arguments in the subtype.
  • Covariance of return types in the subtype.

...

Vedi come la controvarianza è associata all'input e la covarianza è associata all'output? In C # se si contrassegna una variabile di modello con out per renderla covariante, ma si noti che è possibile farlo solo se il parametro di tipo menzionato appare solo come output (tipo restituito dalla funzione). Quindi il seguente non è valido:

interface I<out T>
{
  void func(T t); //Invalid variance: The type parameter 'T' must be
                  //contravariantly valid on 'I<T>.func(T)'.
                  //'T' is covariant.

}

Similare se contrassegni un parametro di tipo con in , ciò significa che puoi usarlo solo come input (parametro funzione). Quindi il seguente non è valido:

interface I<in T>
{
  T func(); //Invalid variance: The type parameter 'T' must
            //be covariantly valid on 'I<T>.func()'. 
            //'T' is contravariant.

}

Quindi, per riassumere, la connessione con la parola chiave out è che con parametri di funzione significa che è un parametro output , e per i parametri di tipo significa che il tipo è usato solo in < em> output contesto.

System.Func è anche un buon esempio di ciò che rwong menzionato nel suo commento. In System.Func tutti i parametri di input sono flaged con in e il parametro di output è impostato su out . Il motivo è esattamente quello che ho descritto.

    
risposta data 10.02.2015 - 08:32
fonte
10

@ Gábor ha già spiegato la connessione (controvarianza per tutto ciò che è "in", covarianza per tutto ciò che è "out"), ma perché riutilizzare le parole chiave?

Bene, le parole chiave sono molto costose. Non puoi usarli come identificativi nei tuoi programmi. Ma ci sono solo tante parole in inglese. Quindi, a volte ti imbatti in conflitti e devi rinominare in modo impacciato variabili, metodi, campi, proprietà, classi, interfacce o strutture per evitare il contrasto con una parola chiave. Ad esempio, se stai modellando una scuola, come si chiama una classe? Non puoi chiamarlo classe, perché class è una parola chiave!

L'aggiunta di una parola chiave a una lingua è persino più costosa. Fondamentalmente rende tutto il codice che usa questa parola chiave come un identificatore illegale, rompendo la retrocompatibilità dappertutto.

Le parole chiave in e out esistevano già, quindi potevano essere riutilizzate.

Loro potrebbero aver aggiunto parole chiave contestuali che sono solo parole chiave nel contesto di un elenco di parametri di tipo, ma quali parole chiave avrebbero scelto? covariant e contravariant ? + e - (come ha fatto Scala, per esempio)? super e extends come ha fatto Java? Potresti ricordare in cima alla tua testa quali parametri sono covarianti e controvarianti?

Con la soluzione corrente, c'è un bel mnemonico: i parametri del tipo di output ottengono la parola chiave out , i parametri del tipo di input ottengono la parola chiave in . Si noti la bella simmetria con i parametri del metodo: i parametri di output ottengono la parola chiave out , i parametri di input ottengono la parola chiave in (beh, in realtà nessuna parola chiave, poiché l'input è l'impostazione predefinita, ma si ottiene l'idea).

[Nota: se si guarda la cronologia delle modifiche, si vedrà che in realtà ho cambiato i due nella mia frase introduttiva. E ho anche avuto un upvote durante quel tempo! Questo ti mostrerà quanto sia importante che il mnemonico sia davvero.]

    
risposta data 10.02.2015 - 13:52
fonte

Leggi altre domande sui tag