Domanda

Ho una classe che ha un tipo generico " G "

Nel mio modello di classe ho

public class DetailElement : ElementDefinition

Diciamo che ho un metodo come questo

        public void DoSomething<G>(G generic)
            where G : ElementDefinition
        {
            if (generic is DetailElement)
            {
                ((DetailElement)generic).DescEN = "Hello people"; //line 1
                //////
                ElementDefinition element = generic;
                ((DetailElement)element).DescEN = "Hello again"; //line 3
                //////
                (generic as DetailElement).DescEN = "Howdy"; //line 5
            }
            else
            {
                //do other stuff
            }
        }

Il compilatore segnala un errore nella riga 1:

Cannot convert type 'G' to 'DetailElement'

Ma la linea 3 funziona bene. Posso risolvere questo problema facendo il codice scritto nella riga 5.

Quello che vorrei sapere è perché il compilatore segnala l'errore nella riga 1 e non quello nella riga 3, dato che, per quanto ne so, sono identici.

modifica: temo che mi manchi qualche pezzo importante della logica del framework

edit2: sebbene le soluzioni per l'errore del compilatore siano importanti, la mia domanda è perché il compilatore segnala un errore nella riga 1 e non nella riga 3.

È stato utile?

Soluzione

Se G è stato costretto a essere un DetailElement ( dove G: DetailElement ), puoi procedere e lanciare G a ElementDefinition, ovvero " (ElementDefinition) generico " ;. Ma poiché G potrebbe essere un'altra sottoclasse di ElementDefinition diversa da DetailElement in fase di esecuzione, non lo consentirà in fase di compilazione in cui il tipo è sconosciuto e non verificabile.

Nella riga 3 il tipo che lanci da è noto come ElementDefinition , quindi tutto ciò che stai facendo è un up-cast . Il compilatore non sa se sarà un cast di successo in fase di esecuzione, ma si fiderà di te lì. Il compilatore non è così fiducioso per i generici.

L'operatore as nella riga 5 potrebbe anche restituire null e il compilatore non controlla staticamente il tipo per vedere se è sicuro in quel caso. Puoi usare come con qualsiasi tipo, non solo quelli compatibili con ElementDefinition .

Da Posso trasmettere da e verso parametri di tipo generico? su MSDN:

  

Il compilatore ti consentirà di trasmettere implicitamente solo parametri di tipo generico su oggetto o su tipi specificati da vincoli.

     

Tale cast implicito è naturalmente sicuro, poiché ogni incompatibilità viene rilevata in fase di compilazione.

     

Il compilatore ti consentirà di trasmettere esplicitamente parametri di tipo generico a qualsiasi interfaccia, ma non a una classe:

   interface ISomeInterface {...}
   class SomeClass {...}
   class MyClass<T> 
    {
      void SomeMethod(T t)
       {
         ISomeInterface obj1 = (ISomeInterface)t;//Compiles
         SomeClass      obj2 = (SomeClass)t;     //Does not compile
       }
    }
     

Tuttavia, puoi forzare un cast da un parametro di tipo generico a qualsiasi altro tipo usando una variabile di oggetto temporanea

 void SomeMethod<T>(T t) 
  { object temp = t;
    MyOtherClass obj = (MyOtherClass)temp;  
  }
     

Inutile dire che tale cast esplicito è pericoloso perché potrebbe generare un'eccezione in fase di esecuzione se il tipo di calcestruzzo utilizzato al posto del parametro di tipo generico non deriva dal tipo in cui viene espressamente eseguito il cast.

     

Invece di rischiare un'eccezione di casting, un approccio migliore consiste nell'utilizzare gli operatori is o come . L'operatore is restituisce true se il parametro di tipo generico è del tipo interrogato e as eseguirà un cast se i tipi sono compatibili e restituirà null in caso contrario.

public void SomeMethod(T t)
 {
   if(t is int) {...}

   string str = t as string;
   if(str != null) {...}
 }

Altri suggerimenti

Generalmente, l'upgrade è un odore di codice. Puoi evitarlo sovraccaricando il metodo. Prova questo:

public void DoSomething(DetailElement detailElement)
{
    // do DetailElement specific stuff
}

public void DoSomething<G>(G elementDefinition)
    where G : ElementDefinition
{
    // do generic ElementDefinition stuff
}

È quindi possibile sfruttare il sovraccarico del metodo utilizzando questo codice:

DetailElement foo = new DetailElement();

DoSomething(foo); // calls the non-generic method
DoSomething((ElementDefinition) foo); // calls the generic method

La tua clausola where non dovrebbe essere " where G: DetailElement " ;?

Nel codice che hai scritto, un DetailElement è un ElementDefinition, ma un ElementDefinition non è necessariamente un DetailElement. Quindi la conversione implicita è illegale.

Esistono altri tipi di ElementDefinition che potresti passare a questo metodo? In tal caso, genereranno un'eccezione quando si tenta di eseguirne il cast nelle istanze DetailElement.

EDIT:

Okay, quindi ora che hai cambiato la tua lista di codici, posso vedere che stai controllando il tipo per assicurarti che sia davvero un DetailElement prima di inserire quel blocco di codice. Sfortunatamente, il fatto è che non puoi implicitamente eseguire il downcast, anche se hai già controllato i tipi da solo. Penso che dovresti davvero usare il " come " parola chiave all'inizio del blocco:

DetailElement detail = generic as DetailElement;
if (detail == null) {
   // process other types of ElementDefinition
} else {
   // process DetailElement objects
}

Meglio ancora, perché non usare il polimorfismo per consentire a ogni tipo di ElementDefinition di definire il proprio metodo DoSomething e lasciare che il CLR si occupi del controllo del tipo e dell'invocazione del metodo per te?

Questo porterà ad un po 'più di codice se hai molte ElementDefinitions di cui sei preoccupato, ma è probabilmente il più fluido che otterrai che non comporta è quindi una sciocchezza.

    public void DoSomething<G>(G generic)
        where G : ElementDefinition
    {
        DetailElement detail = generic as DetailElement;
        if (detail != null)
        {
            detail.DescEN = "Hello people";
        }
        else
        {
            //do other stuff
        }
    }

Un'altra possibile soluzione che ho usato quando avevo bisogno di tali informazioni, in loo di una variabile di oggetto temporanea.

DetailElement detail = (DetailElement)(object)generic;

Funziona, ma la forma as è probabilmente la migliore.

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