2010-08-13 15 views
13

es posible marcar una declaración de método en una interfaz como "new" pero, ¿tiene algún sentido "técnico" o es solo una forma de indicar explícitamente que la declaración no puede anular una anterior?¿Cuál es el propósito de ocultar (usando el modificador "nuevo") una declaración de método de interfaz?

Por ejemplo:

interface II1 
{ 
    new void F(); 
} 

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

es válido (el compilador de C# 4.0 no se queja), pero no parece ser diferente de:

interface II1 
{ 
    void F(); 
} 

interface II2 : II1 
{ 
    void F(); 
} 

Gracias de antemano por cualquier información.

EDIT: ¿Conoces un escenario donde esconderse en una interfaz sería útil?

EDIT: De acuerdo con este enlace: Is method hiding ever a good idea (gracias Scott), el escenario más común parece ser la emulación del tipo de retorno covariantes.

Respuesta

8

El segundo ejemplo emite la siguiente advertencia del compilador:

'II2.F()' Cueros heredado miembro 'II1.F()'. Use la nueva palabra clave si se pretendía ocultar .

Yo diría que la diferencia de usar la palabra clave new es exactamente eso: mostrar intención.

+0

Gracias por su respuesta. Entonces, ¿no afectará la forma en que se pueden usar las interfaces? Editaré la publicación inicial para aclarar esto. – Pragmateek

+1

@Serious: no, la única diferencia es la advertencia del compilador. Yo recomendaría usar la palabra clave 'new' para ser claro. Bueno, no, en realidad recomendaría no ocultar el método en general, ya que me parece confuso, pero si * es necesario *, use la palabra clave 'new'. –

+0

Tiene razón, yo tampoco veo ningún caso de uso interesante donde debería ser necesario. – Pragmateek

6

Los dos son muy diferentes. Al usar 'nuevo' estás creando una nueva cadena de herencia. Esto significa que cualquier implementación de II2 necesitará realizar ambas versiones de F(), y la real a la que usted termine llamando dependerá del tipo de referencia.

considerar los siguientes tres realizaciones:

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() 
     } 
    } 

Si usted tiene una referencia a A2, usted no será capaz de llamar a cualquiera de las versiones de F() sin primer casting a 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 

Si usted tiene una referencia a A3, usted será capaz de llamar a la versión II1 directamente ya que es una implementació implícita:

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

tenga en cuenta que no hay diferencia en el código compilado entre los dos ejemplos dados en la pregunta. La diferencia que explicas es el efecto del ocultamiento de métodos, pero ocurre independientemente de si se usa la palabra clave 'new' o no. –

+0

Gracias por estos excelentes ejemplos. – Pragmateek

+0

@Fredrik: oops, gracias. Por alguna razón, pasé por alto completamente eso. De hecho, eres correcto y la advertencia es la única diferencia. –

4

Sé de un buen uso para esto: tener para hacer esto para declarar una interfaz COM que se deriva de otra interfaz COM. Se trata en este magazine article.

Fwiw, el autor identifica completamente erróneamente la fuente del problema, no tiene nada que ver con un "impuesto a la herencia". COM simplemente aprovecha la forma en que un compilador típico de C++ implementa la herencia múltiple. Hay una v-table para cada clase base, justo lo que COM necesita. El CLR no hace esto, no admite MI y solo hay una sola v-table. Los métodos de interfaz se combinan en la tabla v de la clase base.

+0

Gracias por este buen ejemplo. – Pragmateek

1

El nuevo modificador es muy simple, simplemente suprime la advertencia del compilador que se crea cuando un método está oculto. Si se usa en un método que no oculta otro método, se genera una advertencia.

De The C# Language Specification 3.0

10.3.4 El nuevo modificador Se permite una clase de miembros-declaración para declarar un miembro con el mismo nombre o firma como un miembro heredado. Cuando esto ocurre, se dice que el miembro derivado de la clase oculta el miembro de la clase base. Ocultar un miembro heredado no se considera un error, pero causa que el compilador emita una advertencia. Para suprimir la advertencia, la declaración del miembro de clase derivado puede incluir un nuevo modificador para indicar que el miembro derivado está destinado a ocultar el miembro base. Este tema se analiza con más detalle en §3.7.1.2. Si se incluye un nuevo modificador en una declaración que no oculta un miembro heredado, se emite una advertencia a tal efecto. Esta advertencia se suprime eliminando el nuevo modificador.

1

Un ejemplo después de la respuesta de Fredrik. Quiero tener una interfaz que represente un método para obtener una entidad por ID. Luego deseo que se pueda usar un servicio WCF y otro repositorio estándar como esa interfaz. Para jugar con WCF, necesito decorar mi interfaz con atributos. Desde un punto de vista intencionado, no quiero decorar mi interfaz con cosas de WCF cuando no se va a usar solo a través de WCF, así que necesito usar algo nuevo. Aquí está el código:

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

Interesante, ¿es de un caso de uso real? – Pragmateek

+0

Sí, todos los métodos de recuperación de repositorios se resumen detrás de una interfaz (uno por método). Entonces, cualquier objeto que necesite soportar algún tipo de recuperación puede implementar la interfaz. Algunos de ellos son servicios de WCF que da como resultado el código anterior. – greyalien007

1

Lo uso casi en cada una de mis interfaces. Mira aquí:

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(); 
} 

Es absolutamente normal tener un miembro heredado con menores requisitos, pero las obligaciones de mayor tamaño. LSP no es violado. Pero si es así, ¿dónde documentar el nuevo contrato?

+1

No estoy seguro, en este caso, crear un método 'nuevo' es la forma más adecuada. Para mí, tener un rango de edades diferente debería ser administrado internamente por las implementaciones, que cada uno documentaría sus invariantes. Y si sigo con su ejemplo, no todos los humanos tienen el mismo rango: los patriarcas tienen un rango de 0-999, pero desde entonces el rango es más 0-149. Como dices, este tipo de contrato no puede describirse por el idioma o el tiempo de ejecución, pero puedes usar metadatos como 'System.ComponentModel.DataAnnotations.RangeAttribute'. – Pragmateek

+1

No se trata de rangos, se trata de requisitos y obligaciones. Los rangos son solo los ejemplos más simples de requisitos. La gestión de los requisitos/obligat en las implementaciones da como resultado un código duplicado/no reutilizable. No es obvio en un ejemplo tan simple, pero es inevitable en grandes proyectos con jerarquías profundas. No todos los humanos tienen el mismo rango, depende de tu dominio. En mi universo de abstracciones, es verdad. En el suyo, haga su propia abstracción;) – astef

+0

Sí, pero no todos los requisitos deben expresarse de esta manera. Para tomar otro ejemplo si tengo una interfaz 'Vehículo' con un método' getNumberOfWheels' no hay razón para redefinir un nuevo método porque una bicicleta tiene 2 y un auto 4. La redefinición de un nuevo método es importante si hacen algo diferente desde el punto de vista funcional. – Pragmateek

Cuestiones relacionadas