Domanda

Prendi quanto segue:

class A {}

class B : A {}

class C
{
    C()
    {
        var b = new B();
        Foo(b);
        Foo2(ref b); // <= compile-time error: 
                     // "The 'ref' argument doesn't match the parameter type"
    }

    void Foo(A a) {}

    void Foo2(ref A a) {}  
}

Perché si verifica l'errore di compilazione sopra riportato? Ciò accade con entrambi gli argomenti ref e out .

È stato utile?

Soluzione

=============

AGGIORNAMENTO: ho usato questa risposta come base per questo post di blog:

Perché i parametri ref e out non consentono la variazione del tipo?

Vedi la pagina del blog per ulteriori commenti su questo problema. Grazie per l'ottima domanda.

=============

Supponiamo che tu abbia classi Animal , Mammal , Reptile , Giraffe , Turtle e Tiger , con le evidenti relazioni di sottoclasse.

Ora supponiamo di avere un metodo void M (ref Mammal m) . M può sia leggere che scrivere m .


  

Puoi passare una variabile di tipo Animal a M ?

No. Tale variabile potrebbe contenere un Turtle , ma M supporrà che contenga solo mammiferi. Un Turtle non è un Mammal .

Conclusione 1 : i parametri ref non possono essere resi " ingranditi " ;. (Ci sono più animali che mammiferi, quindi la variabile sta diventando "più grande" perché può contenere più cose.)


  

Puoi passare una variabile di tipo Giraffe a M ?

No. M può scrivere su m e M potrebbe voler scrivere un Tiger in m . Ora hai inserito un Tiger in una variabile che è in realtà del tipo Giraffe .

Conclusione 2 : i parametri ref non possono essere fatti " più piccoli " ;.


Ora considera N (fuori Mammal n) .

  

Puoi passare una variabile di tipo Giraffe a N ?

No. N può scrivere su n e N potrebbe voler scrivere un Tiger .

Conclusione 3 : i parametri out non possono essere resi " più piccoli " ;.


  

Puoi passare una variabile di tipo Animal a N ?

Hmm.

Bene, perché no? N non può leggere da n , può solo scrivergli, giusto? Scrivi un Tiger su una variabile di tipo Animal e sei pronto, giusto?

sbagliato. La regola non è " N può scrivere solo su n " ;.

Le regole sono, brevemente:

1) N deve scrivere su n prima che N ritorni normalmente. (Se N viene lanciato, tutte le scommesse sono disattivate.)

2) N deve scrivere qualcosa su n prima di leggere qualcosa da n .

Ciò consente questa sequenza di eventi:

  • Dichiara un campo x di tipo Animal .
  • Passa x come parametro out a N .
  • N scrive un Tiger in n , che è un alias per x .
  • Su un altro thread, qualcuno scrive un Turtle in x .
  • N tenta di leggere il contenuto di n e scopre un Turtle in ciò che pensa sia una variabile di tipo Mammal .

Chiaramente vogliamo renderlo illegale.

Conclusione 4 : i parametri out non possono essere impostati " ingranditi "


Conclusione finale : Né i parametri ref out possono variare nei loro tipi. Fare altrimenti significa rompere la sicurezza verificabile del tipo.

Se questi problemi nella teoria dei tipi di base ti interessano, potresti leggere la mia serie su come funzionano la covarianza e la contraddizione in C # 4.0 .

Altri suggerimenti

Perché in entrambi i casi devi essere in grado di assegnare valore al parametro ref / out.

Se si tenta di passare b nel metodo Foo2 come riferimento e in Foo2 si tenta di valutare a = new A (), ciò non sarebbe valido.
Stesso motivo per cui non puoi scrivere:

B b = new A();

Stai lottando con il classico problema OOP di covarianza (e contraddizione), vedi wikipedia : per quanto questo fatto possa sfidare le aspettative intuitive, è matematicamente impossibile consentire la sostituzione di classi derivate al posto di quelle di base per argomenti mutabili (assegnabili) (e anche contenitori i cui elementi sono assegnabili, per lo stesso motivo) pur rispettando Principio di Liskov . Perché è così è abbozzato nelle risposte esistenti ed esplorato più approfonditamente in questi articoli wiki e collegamenti da essi.

Le lingue OOP che sembrano farlo mentre restano tradizionalmente tipicamente sicure sono "barare". (inserendo controlli di tipo dinamici nascosti o richiedendo l'esame in fase di compilazione di TUTTE le fonti da verificare); la scelta fondamentale è: o rinunciare a questa covarianza e accettare la perplessità dei praticanti (come fa C # qui), oppure passare a un approccio di tipizzazione dinamica (come ha fatto il primo linguaggio OOP, Smalltalk,) o passare a immutabile (single- assegnazione), come fanno i linguaggi funzionali (sotto l'immutabilità, puoi supportare la covarianza ed evitare anche altri enigmi correlati come il fatto che non puoi avere una sottoclasse quadrata Rectangle in un mondo di dati mutabili).

Si consideri:

class C : A {}
class B : A {}

void Foo2(ref A a) { a = new C(); } 

B b = null;
Foo2(ref b);

Violerebbe la sicurezza dei tipi

Mentre le altre risposte hanno brevemente spiegato il ragionamento alla base di questo comportamento, penso che valga la pena ricordare che se hai davvero bisogno di fare qualcosa di questa natura puoi ottenere funzionalità simili trasformando Foo2 in un metodo generico, come tale:

class A {}

class B : A {}

class C
{
    C()
    {
        var b = new B();
        Foo(b);
        Foo2(ref b); // <= no compile error!
    }

    void Foo(A a) {}

    void Foo2<AType> (ref AType a) where AType: A {}  
}

Perché dare a Foo2 un ref B comporterebbe un oggetto non valido perché Foo2 sa solo come riempire A parte di B .

Il compilatore non ti sta dicendo che vorrebbe che tu lanciassi esplicitamente l'oggetto in modo da poter essere sicuro di sapere quali sono le tue intenzioni?

Foo2(ref (A)b)

Ha senso dal punto di vista della sicurezza, ma l'avrei preferito se il compilatore avesse dato un avvertimento anziché un errore, poiché ci sono usi legittimi di oggetti polimoprimici passati per riferimento. per es.

class Derp : interfaceX
{
   int somevalue=0; //specified that this class contains somevalue by interfaceX
   public Derp(int val)
    {
    somevalue = val;
    }

}


void Foo(ref object obj){
    int result = (interfaceX)obj.somevalue;
    //do stuff to result variable... in my case data access
    obj = Activator.CreateInstance(obj.GetType(), result);
}

main()
{
   Derp x = new Derp();
   Foo(ref Derp);
}

Questo non verrà compilato, ma funzionerebbe?

Se usi esempi pratici per i tuoi tipi, lo vedrai:

SqlConnection connection = new SqlConnection();
Foo(ref connection);

E ora hai la tua funzione che prende il antenato ( cioè Object ):

void Foo2(ref Object connection) { }

Cosa può esserci di sbagliato in questo?

void Foo2(ref Object connection)
{
   connection = new Bitmap();
}

Sei appena riuscito ad assegnare un Bitmap al tuo SqlConnection .

Non va bene.


Riprova con altri:

SqlConnection conn = new SqlConnection();
Foo2(ref conn);

void Foo2(ref DbConnection connection)
{
    conn = new OracleConnection();
}

Hai riempito un OracleConnection sopra il tuo SqlConnection .

Nel mio caso la mia funzione ha accettato un oggetto e non ho potuto inviare nulla, quindi l'ho fatto semplicemente

object bla = myVar;
Foo(ref bla);

E funziona

My Foo è in VB.NET e controlla il tipo all'interno e fa molta logica

Mi scuso se la mia risposta è duplicata ma altre erano troppo lunghe

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