2011-11-01 6 views
18

Durante la lettura di documentazione di Microsoft, mi sono imbattuto in un tale esempio di codice interessante:Perché esiste una restrizione per il casting esplicito di tipo generico a un tipo di classe ma non vi sono restrizioni per trasmettere un tipo generico a un tipo di interfaccia?

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

Significa che è possibile lanciare la vostra generico per l'interfaccia in modo esplicito, ma non alla classe a meno che non si dispone di un vincolo. Bene, non riesco ancora a capire la logica alla base della decisione in quanto sia i casting di tipo di interfaccia che quelli di classe generano eccezioni, quindi perché si dovrebbe proteggere da una sola eccezione?

BTW c'è un modo per aggirare l'errore di compilazione, ma questo non elimina il disordine logica nella mia testa:

class MyOtherClass 
{...} 

class MyClass<T> 
{ 

    void SomeMethod(T t) 

    { 
     object temp = t; 
     MyOtherClass obj = (MyOtherClass)temp; 

    } 
} 
+0

Per curiosità: puoi digitare "SomeClass obj2 = (SomeClass) (object) t;"? –

+0

sì, questo viene eseguito dal secondo snippet –

+1

Controlla questo http://philipm.at/2011/1014/, trovato quando si cerca di trovare la spiegazione. Spoiler alert - potrebbe confondervi di più !;) –

risposta

5

Questo è esattamente ciò che si ottiene in circostanze normali - senza farmaci generici - quando si tenta di lanciare tra le classi senza relazione di ereditarietà:

public interface IA 
{ 
} 

public class B 
{ 
} 

public class C 
{ 
} 

public void SomeMethod(B b) 
{ 
    IA o1 = (IA) b; <-- will compile 
    C o2 = (C)b; <-- won't compile 
} 

Quindi, senza un vincolo, la classe generica si comporterà come se non v'è alcuna relazione tra le classi.

Continua ...

Beh, diciamo che qualcuno fa questo:

public class D : B, IA 
{ 
} 

e quindi chiama:

SomeMethod(new D()); 

Ora vedrete il motivo per cui il compilatore permette al passaggio di interfaccia cast. Non può davvero sapere in fase di compilazione se un'interfaccia è implementata o meno.

Ricorda che la classe D potrebbe benissimo essere scritta da qualcuno che sta usando il tuo assemblaggio - anni dopo averlo compilato. Quindi non c'è alcuna possibilità che il compilatore possa rifiutarsi di compilarlo. Deve essere controllato in fase di esecuzione.

+0

bel punto ma continua a confondere il motivo per cui hanno scelto di consentire un cast e non consentire il secondo? –

+0

Aggiornato la mia risposta per includerlo. –

+0

Se hai provato 'D o3 = (D) b;' in 'SomeMethod (B b)' sarebbe compilato? Mi sembra che la stessa argomentazione valga per il fatto che ciò che si trasmette potrebbe essere un discendente valido per il cast ma che potrebbe non essere ... (e sì, sono pigro e non lo sto provando solo io al momento). – Chris

1

Niente di male. L'unica differenza è che, nel primo caso, il compilatore può rilevare in fase di compilazione che non è possibile eseguire il cast, ma non può essere così "sicuro" delle interfacce, quindi l'errore, in questo caso, aumenterà solo in fase di esecuzione . Quindi,

// Compiles 
ISomeInterface obj1 = (ISomeInterface)t; 

// Сompiles too! 
SomeClass obj2 = (SomeClass)(object)t;  

produrrà gli stessi errori in fase di esecuzione.

Quindi il motivo può essere: il compilatore non sa quale classe di interfacce implementa, ma conosce l'ereditarietà delle classi (quindi il metodo (SomeClass)(object)t funziona). In altre parole: il cast non valido è vietato in CLR, l'unica differenza è che in alcuni casi può essere rilevata in fase di compilazione e in alcuni non può. La ragione principale alla base di ciò, anche se il compilatore conosce tutte le interfacce di classe, non conosce i suoi discendenti, che possono implementarlo, e sono validi per essere T. Si consideri seguente scenario:

namespace ConsoleApplication2 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      MyClass<SomeClass> mc = new MyClass<SomeClass>(); 

      mc.SomeMethod(new SomeClassNested()); 

     } 
    } 

    public interface ISomeInterface 
    { 
    } 

    public class SomeClass 
    { 

    } 

    public class SomeClassNested : SomeClass, ISomeInterface 
    { 

    } 

    public class MyClass<T> 
    { 
     public void SomeMethod(T t) 
     { 
      // Compiles, no errors at runtime 
      ISomeInterface obj1 = (ISomeInterface)t; 
     } 
    } 
} 
+0

perché pensi che non lo sappia? tutto è scritto nell'assieme nelle tabelle dei metadati. –

+0

@Karel bene, tutto è in assembly, questo è sicuro, ma ciò non significa che il compilatore lo sappia. Il compilatore è una cosa relativamente "stupida". –

+0

Puoi collegare qualche riferimento ai compilatori non essendo in grado di scoprire quali interfacce implementano una classe? Mi sembra sbagliato ma sono aperto all'educazione sull'argomento. – Chris

0

penso che la differenza tra casting per un'interfaccia e colata a una classe sta nel fatto che C# supporta più "eredità" solo per le interfacce. Cosa significa?Il compilatore può solo determinare in fase di compilazione se un cast è valido per una classe poiché C# non consente l'ereditarietà multipla per le classi.

D'altra parte il compilatore non sa al momento della compilazione se o la vostra classe non implementa l'interfaccia utilizzata nel cast. Perché? Qualcuno potrebbe ereditare dalla tua classe e implementare l'interfaccia utilizzata nel cast. Quindi, il compilatore non ne è al corrente in fase di compilazione. (Vedere SomeMethod4() di seguito).

Tuttavia, il compilatore è in grado di determinare se un cast su un'interfaccia è valido, se la classe è sigillata.

consideri il seguente esempio:

interface ISomeInterface 
{} 
class SomeClass 
{} 

sealed class SealedClass 
{ 
} 

class OtherClass 
{ 
} 

class DerivedClass : SomeClass, ISomeInterface 
{ 
} 

class MyClass 
{ 
    void OtherMethod(SomeClass s) 
    { 
    ISomeInterface t = (ISomeInterface)s; // Compiles! 
    } 

    void OtherMethod2(SealedClass sc) 
    { 
    ISomeInterface t = (ISomeInterface)sc; // Does not compile! 
    } 

    void OtherMethod3(SomeClass c) 
    { 
    OtherClass oc = (OtherClass)c; // Does not compile because compiler knows 
    }        // that SomeClass does not inherit from OtherClass! 

    void OtherMethod4() 
    { 
    OtherMethod(new DerivedClass()); // In this case the cast to ISomeInterface inside 
    }         // the OtherMethod is valid! 
} 

Lo stesso vale per i farmaci generici.

Spero che questo aiuti.

2

La grande differenza è che un'interfaccia è garantita come un tipo di riferimento. I tipi di valore sono i creatori di problemi. Si è esplicitamente menzionato nel linguaggio C# Specification, capitolo 6.2.6, con un ottimo esempio che illustra il problema:


Le regole di cui sopra non permettono un valido conversione esplicita diretta da un parametro di tipo non vincolata ad un non- tipo di interfaccia, che potrebbe essere sorprendente. La ragione di questa regola è di evitare confusione e rendere chiara la semantica di tali conversioni. Ad esempio, si consideri la seguente dichiarazione:

class X<T> 
{ 
    public static long F(T t) { 
     return (long)t;    // Error 
    } 
} 

Se la conversione esplicita diretta di t a int stato permesso, si potrebbe facilmente aspettare che X.F (7) sarebbe tornato 7L. Tuttavia, non lo sarebbe, perché le conversioni numeriche standard vengono considerate solo quando i tipi sono noti per essere numerici in fase di compilazione. Al fine di rendere la semantica chiaro, l'esempio di cui sopra deve invece essere scritto:

class X<T> 
{ 
    public static long F(T t) { 
     return (long)(object)t;  // Ok, but will only work when T is long 
    } 
} 

Questo codice sarà ora la compilazione, ma l'esecuzione di XF (7) sarebbe quindi un'eccezione in fase di esecuzione, dal momento che un int boxed non può essere convertito direttamente a lungo.

Problemi correlati