2012-01-18 9 views
19

Ho una situazione in cui mi piacerebbe che il comportamento del compilatore fosse spiegato. Dato un po 'di codice:La parola chiave Sealed influenza l'opinione del compilatore su un cast

interface IFoo<T> 
{ 
    T Get(); 
} 

class FooGetter : IFoo<int> 
{ 
    public int Get() 
    { 
     return 42; 
    } 
} 

I seguenti compilato ed eseguito:

static class FooGetterGetter 
{ 
    public static IFoo<T> Get<T>() 
    { 
     return (IFoo<T>)new FooGetter(); 
    } 
} 

Se facciamo una modifica alla firma della classe Foo e aggiungere la parola chiave sealed:

sealed class FooGetter : IFoo<int> // etc 

Quindi viene visualizzato un errore del compilatore nella seguente riga:

return (IFoo<T>)new FooGetter(); 

Di:

Impossibile convertire il tipo 'MyNamespace.FooGetter' a 'MyNamespace.IFoo <T>'

Qualcuno può spiegare che cosa sta accadendo qui per quanto riguarda la parola chiave sealed? Questo è il C# 4 contro un progetto .NET 4 in Visual Studio 2010.

Aggiornamento: abbastanza interessante mi sono imbattuto in quella parte del comportamento quando mi chiedevo perché il seguente codice corregge quando viene applicata sealed:

return (IFoo<T>)(IFoo<int>)new FooGetter(); 

Aggiornamento: solo per un chiarimento, tutto funziona bene quando il tipo di T richiesto è lo stesso come il tipo di T utilizzato dal tipo concreto. Se i tipi sono diversi, il cast non riesce a runtime con qualcosa di simile:

Impossibile eseguire il cast oggetto di tipo 'MyNamespace.StringFoo' di digitare 'MyNamespace.IFoo`1 [System.Int32]'

Nell'esempio sopra, StringFoo : IFoo<string> e il chiamante chiede di ottenere uno int.

+2

Non ho la risposta, ma immagino che ha qualcosa a che fare con il fatto che '' IFoo è un tipo generico aperta mentre 'attrezzi FooGetter' 'IFoo ' che è un tipo generico chiuso. –

+1

Solo una nota: mi sono assicurato che avessi definito il comportamento prima di postare la domanda - non volevo prendere in giro me stesso :-) Posso capire perché potrebbe essere permesso, il compilatore non può garantire cosa sta succedendo, solo sa che ha una possibilità di successo. Ma per qualche ragione, rimuove la stessa possibilità di successo quando la parola chiave sigillata è presente a causa del presupposto che, poiché non può essere derivata, non può corrispondere a T. –

+0

+1 Interessante :) – leppie

risposta

8

Perché l'FooGetter è un'implementazione esplicita di IFoo<int> invece di implementare IFoo<T> genericamente. Dal momento che è sigillato, il compilatore sa che non c'è modo di lanciarlo su un generico IFoo<T> se T è qualcosa di diverso da un int. Se non fosse sigillato, il compilatore gli consentirebbe di compilare e generare un'eccezione in fase di runtime se T non fosse uno int.

Se si tenta di utilizzare con qualcosa di diverso da un int (ad es FooGetterGetter.Get<double>();) si ottiene un'eccezione:

Impossibile eseguire il cast oggetto di tipo 'MyNamespace.FooGetter' digitare 'MyNamespace.IFoo`1 [System.Double]'.

Quello che non sono sicuro del motivo il compilatore non non genera un errore per la versione non sigillata.Come potrebbe la tua sottoclasse in modo tale che new FooGetter() ti dia qualcosa che implementa IFoo<{something_other_than_int}>?

Aggiornamento:

Per Dan Bryant e Andras Zoltan ci sono metodi per restituire una classe derivata da un costruttore (o forse più precisamente per il compilatore per restituire un tipo diverso analizzando attributi). Quindi tecnicamente questo è fattibile se la classe non è sigillata.

+4

+1 ma per rispondere alla tua ultima domanda - beh - è un'interfaccia - quindi puoi semplicemente aggiungere un'altra istanza dell'interfaccia? Il compilatore non vede il 'new', vede solo un'espressione di tipo' FooGetter' che, quando non 'sealed' può essere qualsiasi tipo derivato da esso. –

+0

@DStanley Vedo il tuo punto. La tua spiegazione quindi si applica anche al casting due volte per aggirare la parola chiave chiusa. Nella situazione cast-due, sai che puoi trasmettere a un 'IFoo ' se il tipo concreto usa 'int', puoi quindi lanciare da' IFoo 'a' IFoo 'per lo stesso motivo per cui il compilatore ti permette di fare 'FooGetter' a' IFoo '(se non fosse sigillato) - per qualche ragione lascia che si verifichino errori in fase di esecuzione. –

+3

Per dare corpo al punto di Andras, potresti avere "SecondFoo: FooGetter, IFoo ". Curioso del motivo per cui ignora il 'new' in questo caso, che non garantisce tipi derivati ​​... a meno che non assuma che un tipo di base possa eventualmente implementare l'interfaccia corretta e non si preoccupi di guardare - lasciando errori in runtime. –

4

Quando una classe sigillata qualsiasi classe derivata potrebbe implementare IFoo<T>:

class MyClass : FooGetter, IFoo<double> { } 

Quando FooGetter è contrassegnato come sigillato, il compilatore sa che non può essere possibile per eventuali ulteriori implementazioni di IFoo<T> diversi IFoo<int> potrebbero esistere per FooGetter.

Questo è un buon comportamento, consente di rilevare problemi con il codice in fase di compilazione anziché in fase di esecuzione.

Il motivo per cui lo (IFoo<T>)(IFoo<int>)new FooGetter(); funziona è perché ora si rappresenta la classe sigillata come IFoo<int> che potrebbe essere implementata da qualsiasi cosa. È anche un buon lavoro in giro perché non si è accidentalmente, ma sovrascrivendo di proposito il controllo del compilatore.

1

Giusto per aggiungere alle risposte esistenti: Questo in realtà non ha nulla a che fare con i generici usati.

Si consideri l'esempio più semplice:

interface ISomething 
{ 
} 

class OtherThing 
{ 
} 

poi dire (all'interno di un metodo):

OtherThing ot = XXX; 
ISomething st = (ISomething)ot; 

funziona bene. Il compilatore non sa se un OtherThing potrebbe essere un ISomething, quindi ci crede quando diciamo che avrà successo. Tuttavia, se si modifica OtherThing in un tipo sigillato (ovvero sealed class OtherThing { } o struct OtherThing { }), il cast non è più consentito. Il compilatore sa che non può andare bene (tranne se ot doveva essere null, ma le regole di C# non consentono ancora il cast da un tipo sealed a un'interfaccia non implementata da quel tipo sealed).

Riguardo all'aggiornamento della domanda: Scrivere (IFoo<T>)(IFoo<int>)new FooGetter() non è molto diverso da scrivere (IFoo<T>)(object)new FooGetter(). Puoi "rendere permesso" qualsiasi cast (con generici o senza) passando per un tipo intermedio che è certamente/possibilmente un antenato di entrambi i tipi che vuoi convertire. E 'molto simile a questo modello:

void MyMethod<T>(T t) // no "where" constraints on T 
{ 
    if (typeof(T) = typeof(GreatType)) 
    { 
    var tConverted = (GreatType)(object)t; 
    // ... use tConverted here 
    } 
    // ... other stuff 
} 
Problemi correlati