2010-08-13 12 views
13

è possibile contrassegnare una dichiarazione di metodo in un'interfaccia come "new" ma ha un senso "tecnico" o è solo un modo per dichiarare esplicitamente che la dichiarazione non può sovrascrivere quella precedente?Qual è lo scopo di nascondere (usando il "nuovo" modificatore) una dichiarazione del metodo di interfaccia?

Ad esempio:

interface II1 
{ 
    new void F(); 
} 

interface II2 : II1 
{ 
    new void F(); 
} 

è valido (il compilatore C# 4.0 non si lamenta), ma non sembra essere diverso da:

interface II1 
{ 
    void F(); 
} 

interface II2 : II1 
{ 
    void F(); 
} 

Grazie in anticipo per qualsiasi informazione.

MODIFICA: sai uno scenario in cui nascondere in un'interfaccia sarebbe utile?

EDIT: Secondo questo link: Is method hiding ever a good idea (grazie Scott), lo scenario più comune sembra essere l'emulazione di tipo restituito covariante.

risposta

8

Il secondo esempio emette il seguente avviso del compilatore:

'II2.F()' nasconde ereditato membro 'II1.F()'. Utilizzare la nuova parola chiave se era inteso il nascondimento .

Direi che la differenza nell'utilizzo della parola chiave new è esattamente questa: mostrare l'intenzione.

+0

Grazie per la risposta. Quindi non influenzerà il modo in cui le interfacce possono essere utilizzate? Modificherò il post iniziale per chiarire questo. – Pragmateek

+1

@Serious: no, l'unica differenza è l'avviso del compilatore. Consiglierei di usare la parola chiave 'new' per essere chiari. Beh, no, in realtà mi consiglierei di non nascondermi il metodo in generale, dato che trovo confuso, ma se * necessario *, usa la parola chiave 'new'. –

+0

Hai ragione, anch'io non vedo nessun caso d'uso interessante dove dovrebbe essere necessario. – Pragmateek

6

I due sono molto diversi. Usando 'nuovo' stai creando una nuova catena di ereditarietà. Ciò significa che qualsiasi implementazione di II2 dovrà realizzare entrambe le versioni di F() e quella effettiva che si finisce per chiamare dipenderà dal tipo di riferimento.

considerare i seguenti tre realizzazioni:

class A1 : II1 
    { 
     public void F() 
     { 
      // realizes II1.F() 
     } 
    } 

    class A2 : II2 
    { 
     void II1.F() 
     { 
      // realizes II1.F() 
     } 

     void II2.F() 
     { 
      // realizes II2.F() 
     } 
    } 

    class A3 : II2 
    { 
     public void F() 
     { 
      // realizes II1.F() 
     } 

     void II2.F() 
     { 
      // realizes II2.F() 
     } 
    } 

Se si dispone di un riferimento a A2, non sarà in grado di chiamare entrambe le versioni di F() senza prima casting per II1 o II2.

A2 a2 = new A2(); 
a2.F(); // invalid as both are explicitly implemented 
((II1) a2).F(); // calls the II1 implementation 
((II2) a2).F(); // calls the II2 implementation 

Se si dispone di un riferimento a A3, si sarà in grado di chiamare direttamente la versione II1 in quanto è un implentation implicita:

A3 a3 = new A3(); 
a3.F(); // calls the II1 implementation 
((II2) a3).F(); // calls the II2 implementation 
+2

nota che non vi è alcuna differenza nel codice compilato tra i due esempi riportati nella domanda. La differenza che spieghi è l'effetto del metodo che si nasconde, ma succede indipendentemente dall'uso della parola chiave 'new' o meno. –

+0

Grazie per questi grandi esempi. – Pragmateek

+0

@Fredrik: oops, grazie. Per qualche ragione l'ho completamente ignorato. Sei davvero corretto e l'avvertimento è l'unica differenza. –

4

So di un buon uso per questo: voi avere per fare ciò per dichiarare un'interfaccia COM derivata da un'altra interfaccia COM. È toccato in questo magazine article.

Fwiw, l'autore identifica erroneamente la fonte del problema, non ha nulla a che fare con una "tassa di successione". COM semplicemente sfrutta il modo in cui un tipico compilatore C++ implementa l'ereditarietà multipla. C'è una tabella v per ogni classe base, solo ciò di cui COM ha bisogno. Il CLR non lo fa, non supporta l'MI e c'è sempre un solo v-table. I metodi di interfaccia sono uniti nella v-table della classe base.

+0

Grazie per questo buon esempio. – Pragmateek

1

Il nuovo modificatore è molto semplice, sopprime solo l'avviso del compilatore che viene creato quando un metodo è nascosto. Se utilizzato su un metodo che non nasconde un altro metodo, viene generato un avviso.

Da The C# Language Specification 3.0

10.3.4 Il nuovo modificatore di Una classe-membro-dichiarazione è permesso di dichiarare un membro con lo stesso nome o la firma come membro ereditato. Quando ciò accade, si dice che il membro della classe derivata nasconda il membro della classe base. Nascondere un membro ereditato non è considerato un errore, ma fa sì che il compilatore emetta un avviso. Per sopprimere l'avviso, la dichiarazione del membro della classe derivata può includere un nuovo modificatore per indicare che il membro derivato è destinato a nascondere il membro di base. Questo argomento è discusso ulteriormente in §3.7.1.2. Se un nuovo modificatore è incluso in una dichiarazione che non nasconde un membro ereditato, viene emesso un avviso a tale effetto. Questo avvertimento è soppresso rimuovendo il nuovo modificatore.

1

Un esempio che segue la risposta di Fredrik. Voglio avere un'interfaccia che rappresenti un metodo per ottenere un'entità tramite id. Quindi desidero sia un servizio WCF che un altro repository standard in grado di essere utilizzato come tale interfaccia. Per giocare con WCF, ho bisogno di decorare la mia interfaccia con gli attributi. Da un punto di vista intenzionale, non voglio decorare la mia interfaccia con le cose WCF quando non verrà utilizzata solo con WCF, quindi ho bisogno di usarne di nuove. Ecco il codice:

public class Dog 
{ 
    public int Id { get; set; } 
} 

public interface IGetById<TEntity> 
{ 
    TEntity GetById(int id); 
} 

public interface IDogRepository : IGetById<Dog> { } 

public class DogRepository : IDogRepository 
{ 
    public Dog GetById(int id) 
    { 
     throw new NotImplementedException(); 
    } 
} 

[ServiceContract] 
public interface IWcfService : IGetById<Dog> 
{ 
    [OperationContract(Name="GetDogById")] 
    new Dog GetById(int id); 
} 
+0

Interessante, proviene da un caso d'uso reale? – Pragmateek

+0

Sì, tutti i miei metodi di recupero del repository sono astratti dietro un'interfaccia (uno per metodo). Quindi qualsiasi oggetto che deve supportare una sorta di recupero può implementare l'interfaccia. Alcuni di questi sono servizi WCF che risultano nel codice sopra. – greyalien007

1

Lo uso quasi in ogni mia interfaccia. Guarda qui:

interface ICreature 
{ 
    /// <summary> 
    ///  Creature's age 
    /// </summary> 
    /// <returns> 
    ///  From 0 to int.Max 
    /// </returns> 
    int GetAge(); 
} 

interface IHuman : ICreature 
{ 
    /// <summary> 
    ///  Human's age 
    /// </summary> 
    /// <returns> 
    ///  From 0 to 999 
    /// </returns> 
    new int GetAge(); 
} 

È assolutamente normale avere un membro ereditato con requisiti minori ma obblighi maggiori. LSP non è violato. Ma se sì, dove documentare il nuovo contratto?

+1

Non è sicuro che in questo caso la creazione di un metodo 'new' sia il modo più appropriato. Per me avere una diversa fascia di età dovrebbe essere gestito internamente dalle implementazioni, che documenterebbero i loro invarianti. E se continuo con il tuo esempio, non tutti gli umani hanno lo stesso raggio d'azione: i patriarchi hanno in effetti un range 0-999 ma da allora il range è più 0-149. Come dici tu, questo tipo di contratto non può essere descritto dalla lingua o dal runtime stesso, ma puoi usare metadati come "System.ComponentModel.DataAnnotations".RangeAttribute'. – Pragmateek

+1

Non si tratta di intervalli, si tratta di requisiti e obblighi. Le gamme sono solo gli esempi più semplici di requisiti. La gestione di quei req/obl nelle implementazioni porta a un codice duplicato/non riutilizzabile. Non è ovvio in un esempio così semplice, ma è inevitabile in grandi progetti con gerarchie profonde. A proposito di non tutti gli umani hanno lo stesso raggio d'azione - dipende dal tuo dominio. Nel mio universo di astrazioni - è vero. Nel tuo - crea la tua astrazione;) – astef

+0

Sì, ma non tutti i requisiti dovrebbero essere espressi in questo modo. Per fare un altro esempio se ho un'interfaccia 'Vehicle' con un metodo' getNumberOfWheels' non c'è motivo di ridefinire un nuovo metodo perché una bici ha 2 e una macchina 4. La ridefinizione di un nuovo metodo è importante se fanno qualcosa di diverso. – Pragmateek

Problemi correlati