Compartir a través de


19 Interfaces

19.1 General

Una interfaz define un contrato. Una clase o estructura que implementa una interfaz debe cumplir su contrato. Una interfaz puede heredar de varias interfaces base, y una clase o estructura puede implementar varias interfaces.

Las interfaces pueden contener varios tipos de miembros, como se describe en §19.4. La propia interfaz puede proporcionar una implementación para algunos o todos los miembros de función que declara. Los miembros para los que la interfaz no proporciona una implementación son abstractas. Sus implementaciones deben proporcionarse mediante clases o estructuras que implementan la interfaz o la interfaz derivada que proporcionan una definición de invalidación.

Nota: Históricamente, agregar un nuevo miembro de función a una interfaz afectaba a todos los consumidores existentes de ese tipo de interfaz; fue un cambio importante. La adición de implementaciones de miembros de función de interfaz permitió a los desarrolladores actualizar una interfaz, al tiempo que permitía a los implementadores invalidar esa implementación. Los usuarios de la interfaz pueden aceptar la implementación como un cambio no importante; sin embargo, si sus requisitos son diferentes, pueden invalidar las implementaciones proporcionadas. nota final

19.2 Declaraciones de interfaz

19.2.1 General

Una interface_declaration es una type_declaration (sección 14.7) que declara un nuevo tipo de interfaz.

interface_declaration
    : attributes? interface_modifier* 'partial'? 'interface'
      identifier variant_type_parameter_list? interface_base?
      type_parameter_constraints_clause* interface_body ';'?
    ;

Un interface_declaration consta de un conjunto opcional de atributos (§23), seguido de un conjunto opcional de interface_modifiers (§19.2.2), seguido de un modificador parcial opcional (§15.2.7), seguido de la palabra clave interface y un identificador que denomina la interfaz, seguido de una especificación opcional de variant_type_parameter_list (§19.2.3), seguida de una especificación de interface_base opcional (§19.2.4)), seguido de una especificación de type_parameter_constraints_clauseopcional (§15.2.5), seguida de un interface_body (§19.3), seguido opcionalmente de un punto y coma.

Una declaración de interfaz no proporcionará type_parameter_constraints_clausea menos que también proporcione una variant_type_parameter_list.

Una declaración de interfaz que proporciona una variant_type_parameter_list es una declaración de interfaz genérica. Además, cualquier interfaz anidada dentro de una declaración de clase genérica o una declaración de estructura genérica es una declaración de interfaz genérica, ya que se proporcionarán argumentos de tipo para el tipo contenedor para crear un tipo construido (sección 8.4).

Modificadores de interfaz 19.2.2

Una interface_declaration puede incluir opcionalmente una secuencia de modificadores de interfaz:

interface_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | unsafe_modifier   // unsafe code support
    ;

unsafe_modifier (§24.2) solo está disponible en código no seguro (§24).

Es un error en tiempo de compilación porque el mismo modificador aparece varias veces en una declaración de interfaz.

El modificador new solo se permite en interfaces definidas dentro de una clase. Especifica que la interfaz oculta un miembro heredado con el mismo nombre, como se describe en la sección 15.3.5.

Los modificadores public, protected, internal y private controlan la accesibilidad de la interfaz. En función del contexto en el que se produzca la declaración de interfaz, solo se pueden permitir algunos de estos modificadores (sección 7.5.2). Cuando una declaración de tipo parcial (sección 15.2.7) incluye una especificación de accesibilidad (a través de los modificadores public, protected, internal y private), se aplican las reglas de la sección 15.2.2.

19.2.3 Listas de parámetros de tipo variant

19.2.3.1 General

Las listas de parámetros de tipo de variante solo pueden producirse en tipos de interfaz y delegado. La diferencia entre los type_parameter_listnormales es la variance_annotation opcional en cada parámetro de tipo.

variant_type_parameter_list
    : '<' variant_type_parameter (',' variant_type_parameter)* '>'
    ;

variant_type_parameter
    : attributes? variance_annotation? type_parameter
    ;

variance_annotation
    : 'in'
    | 'out'
    ;

Si la anotación de varianza es out, se dice que el parámetro de tipo es covariante. Si la anotación de varianza es in, se dice que el parámetro de tipo es contravariante. Si no hay ninguna anotación de varianza, se dice que el parámetro de tipo es invariable..

Ejemplo: En el siguiente:

interface C<out X, in Y, Z>
{
    X M(Y y);
    Z P { get; set; }
}

X es covariante, Y es contravariante y Z es invariable.

ejemplo final

Si una interfaz genérica se declara en varias partes (§15.2.3), cada declaración parcial especificará la misma varianza para cada parámetro de tipo.

19.2.3.2 Seguridad de varianza

La aparición de anotaciones de varianza en la lista de parámetros de tipo de un tipo restringe los lugares donde pueden producirse los tipos dentro de la declaración de tipo.

Un tipo T es no seguro para la salida si se cumple una de las siguientes condiciones:

  • T es un parámetro de tipo contravariante
  • T es un tipo de matriz con un tipo de elemento no seguro para la salida
  • T es un tipo Sᵢ,... Aₑ de interfaz o delegado construido a partir de un tipo S<Xᵢ, ... Xₑ> genérico en el que al menos uno Aᵢ de los siguientes contiene:
    • Xᵢ es covariante o invariable y Aᵢ no es seguro para la salida.
    • Xᵢ es contravariante o invariable y Aᵢ no es seguro para la entrada.

Un tipo T es no seguro para la entrada si se cumple una de las siguientes condiciones:

  • T es un parámetro de tipo covariante.
  • T es un tipo de matriz con un tipo de elemento no seguro para la entrada
  • T es un tipo S<Aᵢ,... Aₑ> de interfaz o delegado construido a partir de un tipo S<Xᵢ, ... Xₑ> genérico en el que al menos uno Aᵢ de los siguientes contiene:
    • Xᵢ es covariante o invariable y Aᵢ no es seguro para la entrada.
    • Xᵢ es contravariante o invariable y Aᵢ no es seguro para la salida.

Intuitivamente, un tipo no seguro para la salida está prohibido en una posición de salida y un tipo no seguro para la entrada está prohibido en una posición de entrada.

Un tipo es seguro para la salida si no es inseguro para la salida y es seguro para la entrada si no es inseguro para la entrada.

19.2.3.3 Conversión de varianza

El propósito de las anotaciones de varianza es proporcionar conversiones más lenientes (pero todavía seguras para los tipos) en los tipos de interfaz y de delegado. Para ello, las definiciones de las conversiones implícitas (sección 10.2) y explícitas (sección 10.3) hacen uso de la noción de convertibilidad de varianza, que se define de la siguiente manera:

Un tipo T<Aᵢ, ..., Aᵥ> es de varianza convertible a un tipo T<Bᵢ, ..., Bᵥ> si T es un tipo de interfaz o delegado declarado con los parámetros de tipo variante T<Xᵢ, ..., Xᵥ> y para cada parámetro de tipo variante Xᵢ se cumple una de las siguientes condiciones:

  • Xᵢ es covariante y existe una referencia implícita o conversión de identidad de Aᵢ a Bᵢ
  • Xᵢ es contravariante y existe una referencia implícita o conversión de identidad de Bᵢ a Aᵢ
  • Xᵢ es invariable y existe una conversión de identidad de Aᵢ a Bᵢ

19.2.4 Interfaces base

Una interfaz puede heredar de cero o más tipos de interfaz, que se denominan interfaces base explícitasde la interfaz. Cuando una interfaz tiene una o varias interfaces base explícitas, en la declaración de esa interfaz, el identificador de interfaz va seguido de dos puntos y una lista separada por comas de tipos de interfaz base.

Una interfaz derivada puede declarar nuevos miembros que ocultan miembros heredados (§7.7.2.3) declarados en interfaces base o implementan explícitamente miembros heredados (§19.6.2) declarados en interfaces base.

interface_base
    : ':' interface_type_list
    ;

Las interfaces base explícitas se pueden construir tipos de interfaz (§8.4, §19.2). Una interfaz base no puede ser un parámetro de tipo por si misma, aunque puede implicar los parámetros de tipo que están en el ámbito.

Para un tipo de interfaz construido, las interfaces base explícitas se forman tomando las declaraciones de interfaz base explícitas en la declaración de tipo genérico y sustituyendo, por cada type_parameter en la declaración de interfaz base, el type_argument correspondiente del tipo construido.

Las interfaces base explícitas de una interfaz deben ser al menos igual de accesibles que la interfaz (sección 7.5.5).

Nota: Por ejemplo, es un error en tiempo de compilación especificar una interfaz private o internal en la interface_base de una interfaz public. nota final

Es un error de compilación que una interfaz herede directa o indirectamente de sí misma.

Las interfaces basede una interfaz son las interfaces base explícitas y sus interfaces base. En otras palabras, el conjunto de interfaces base es el cierre transitivo completo de las interfaces base explícitas, de sus propias interfaces base explícitas, y así sucesivamente. Una interfaz hereda todos los miembros de sus interfaces base.

Example: En el código de ejemplo siguiente

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

interface IListBox : IControl
{
    void SetItems(string[] items);
}

interface IComboBox: ITextBox, IListBox {}

las interfaces base de IComboBox son IControl, ITextBox y IListBox. En otras palabras, la interfaz IComboBox anterior hereda los miembros SetText y SetItems, así como Paint.

ejemplo final

Los miembros heredados de un tipo genérico construido se heredan después de la sustitución de tipos. Es decir, en los tipos constituyentes del miembro se sustituyen los parámetros tipo de la declaración de clase base por los argumentos de tipo correspondientes usados en la especificación de class_base .

Example: En el código de ejemplo siguiente

interface IBase<T>
{
    T[] Combine(T a, T b);
}

interface IDerived : IBase<string[,]>
{
    // Inherited: string[][,] Combine(string[,] a, string[,] b);
}

la interfaz IDerived hereda el método Combine después de sustituirse el parámetro de tipo T por string[,].

ejemplo final

Una clase o estructura que implementa una interfaz también implementa implícitamente todas las interfaces base de la interfaz.

El control de interfaces en varias partes de una declaración de interfaz parcial (sección 15.2.7) se describe más adelante en la sección 15.2.4.3.

Cada interfaz base de una interfaz será segura para salidas (§19.2.3.2).

Cuerpo de la interfaz 19.3

El interface_body de una interfaz define los miembros de la interfaz.

interface_body
    : '{' interface_member_declaration* '}'
    ;

19.4 Miembros de la interfaz

19.4.1 General

Los miembros de una interfaz son los miembros heredados de las interfaces base y los miembros declarados por la propia interfaz.

interface_member_declaration
    : constant_declaration
    | field_declaration
    | method_declaration
    | property_declaration
    | event_declaration
    | indexer_declaration
    | static_constructor_declaration
    | operator_declaration
    | type_declaration
    ;

Esta cláusula aumenta la descripción de los miembros de las clases (§15.3) con restricciones para las interfaces. Los miembros de interfaz se declaran mediante member_declarations con las siguientes reglas adicionales:

  • No se permite un finalizer_declaration .
  • No se permiten constructores de instancia, constructor_declarations.
  • Todos los miembros de la interfaz tienen implícitamente acceso público; sin embargo, se permite un modificador de acceso explícito (§7.5.2), excepto en constructores estáticos (§15.12).
  • El abstract modificador está implícito para los miembros de función de interfaz sin cuerpos; ese modificador se puede dar explícitamente.
  • Un miembro de función de instancia de interfaz cuya declaración incluye un cuerpo es un miembro implícitamente virtual a menos que se use el sealed modificador o private . El virtual modificador se puede dar explícitamente.
  • Un private miembro de función o sealed de una interfaz tendrá un cuerpo.
  • Un private miembro de función no tendrá el modificador sealed.
  • Una interfaz derivada puede invalidar un miembro abstracto o virtual declarado en una interfaz base.
  • Un miembro de función implementado explícitamente no tendrá el modificador sealed.

Algunas declaraciones, como constant_declaration (§15.4) no tienen restricciones en las interfaces.

Los miembros heredados de una interfaz no forman parte específicamente del espacio de declaración de la interfaz. Por lo tanto, se permite que una interfaz declare un miembro con el mismo nombre o firma que un miembro heredado. Cuando esto ocurre, se dice que el miembro de interfaz derivada oculta el miembro de interfaz base. Ocultar un miembro heredado no se considera un error, pero produce una advertencia (§7.7.2.3).

Si se incluye un new modificador en una declaración que no oculta un miembro heredado, se emite una advertencia en consecuencia.

Nota: Los miembros de la clase object no son, estrictamente hablando, miembros de ninguna interfaz (§19.4). Sin embargo, los miembros de la clase object están disponibles a través de la búsqueda de miembros en cualquier tipo de interfaz (sección 12.5). nota final

El conjunto de miembros de una interfaz declarada en varias partes (sección 15.2.7) es la unión de los miembros declarados en cada parte. Los cuerpos de todas las partes de la declaración de interfaz comparten el mismo espacio de declaración (sección 7.3) y el ámbito de cada miembro (sección 7.7) se extiende a los cuerpos de todas las partes.

Ejemplo: considere una interfaz IA con una implementación para un miembro M y una propiedad P. Un tipo C de implementación no proporciona una implementación para M o P. Se debe tener acceso a ellos a través de una referencia cuyo tipo en tiempo de compilación es una interfaz que se puede convertir implícitamente en IA o IB. Estos miembros no se encuentran a través de la búsqueda de miembros en una variable de tipo C.

interface IA
{
    public int P { get { return 10; } }
    public void M()
    {
        Console.WriteLine("IA.M");
    }
}

interface IB : IA
{
    public new int P { get { return 20; } }
    void IA.M()
    {
        Console.WriteLine("IB.M");
    }
}

class C : IB { }

class Test
{
    public static void Main()
    {
        C c = new C();
        ((IA)c).M();                               // cast needed
        Console.WriteLine($"IA.P = {((IA)c).P}");  // cast needed
        Console.WriteLine($"IB.P = {((IB)c).P}");  // cast needed
    }
}

Dentro de las interfaces IA y IB, el miembro M es accesible directamente por su nombre. Sin embargo, dentro del método Main, no podemos escribir c.M() o c.P, ya que esos nombres no son visibles. Para encontrarlos, se necesitan conversiones al tipo de interfaz adecuado. La declaración de en M usa la sintaxis de implementación de IB interfaz explícita. Esto es necesario para que ese método invalide el IAde ; es posible que el modificador override no se aplique a un miembro de función. ejemplo final

19.4.2 Campos de interfaz

Esta cláusula aumenta la descripción de los campos de las clases §15.5 para los campos declarados en interfaces.

Los campos de interfaz se declaran mediante field_declarations (§15.5.1) con las siguientes reglas adicionales:

  • Es un error en tiempo de compilación para field_declaration declarar un campo de instancia.

Ejemplo: El siguiente programa contiene miembros estáticos de varios tipos:

public interface IX
{
    public const int Constant = 100;
    protected static int field;

    static IX()
    {
        Console.WriteLine("static members initialized");
        Console.WriteLine($"constant = {IX.Constant}, field = {IX.field}");
        field = 50;
        Console.WriteLine("static constructor has run");
    }
}

public class Test: IX
{
    public static void Main()
    {
        Console.WriteLine($"constant = {IX.Constant}, field = {IX.field}");
    }
}

La salida generada es

static members initialized
constant = 100, field = 0
static constructor has run
constant = 100, field = 50

ejemplo final

Consulte §19.4.8 para obtener información sobre la asignación e inicialización de campos estáticos.

Métodos de interfaz 19.4.3

Esta cláusula aumenta la descripción de los métodos de las clases §15.6 para los métodos declarados en interfaces.

Los métodos de interfaz se declaran mediante method_declarations (§15.6)). Los atributos, return_type, ref_return_type, identificador y parameter_list de una declaración de método de interfaz tienen el mismo significado que los de una declaración de método en una clase. Los métodos de interfaz tienen las siguientes reglas adicionales:

  • method_modifier no incluirá override.

  • Un método cuyo cuerpo es un punto y coma (;) es abstract; el abstract modificador no es necesario, pero se permite.

  • Una declaración de método de interfaz que tiene un cuerpo de bloque o un cuerpo de expresión como un method_body es virtual; el virtual modificador no es necesario, pero se permite.

  • Un method_declaration no tendrá type_parameter_constraints_clausea menos que también tenga un type_parameter_list.

  • La lista de requisitos para combinaciones válidas de modificadores indicados para un método de clase se extiende de la siguiente manera:

    • Una declaración estática que no sea extern tendrá un cuerpo de bloque o un cuerpo de expresión como method_body.
    • Una declaración virtual que no sea extern tendrá un cuerpo de bloque o un cuerpo de expresión como method_body.
    • Una declaración privada que no sea extern tendrá un cuerpo de bloque o un cuerpo de expresión como method_body.
    • Una declaración sellada que no sea extern tendrá un cuerpo de bloque o cuerpo de expresión como method_body.
    • Una declaración asincrónica tendrá un cuerpo de bloque o un cuerpo de expresión como method_body.
  • Todos los tipos de parámetros de un método de interfaz serán seguros para la entrada (§19.2.3.2) y el tipo de valor devuelto será o void seguro para la salida.

  • Cualquier tipo de parámetro de salida o referencia también será seguro para la salida.

    Nota: Los parámetros de salida deben ser seguros para la entrada debido a restricciones comunes de implementación. nota final

  • Cada restricción de tipo de clase, restricción de tipo de interfaz y restricción de parámetro de tipo en cualquier parámetro de tipo del método será segura para la entrada.

Estas reglas garantizan que cualquier uso covariante o contravariante de la interfaz siga siendo segura en cuanto a tipo.

Ejemplo:

interface I<out T>
{
    void M<U>() where U : T;     // Error
}

tiene un formato incorrecto porque el uso de T como restricción de parámetro de tipo en U no es seguro para la entrada.

Si esta restricción no estuviera en efecto, sería posible infringir la seguridad de tipos de la siguiente manera:

interface I<out T>
{
    void M<U>() where U : T;
}
class B {}
class D : B {}
class E : B {}
class C : I<D>
{
    public void M<D>() {...} 
}

...

I<B> b = new C();
b.M<E>();

Esto es realmente una llamada a C.M<E>. Pero esa llamada requiere que E derive de D, por lo que en este caso se infringiría la seguridad de tipo.

ejemplo final

Nota: Vea §19.4.2 para ver un ejemplo que no solo muestra un método estático con una implementación, sino que se llama Main a ese método y tiene el tipo de valor devuelto y la firma correctos, también es un punto de entrada. nota final

Un método virtual con la implementación declarada en una interfaz se puede invalidar para que sea abstracta en una interfaz derivada. Esto se conoce como reabstración.

Ejemplo:

interface IA
{
    void M() { Console.WriteLine("IA.M"); }
}

interface IB: IA
{
    abstract void IA.M();    // reabstraction of M
}

Esto resulta útil en interfaces derivadas en las que la implementación de un método es inapropiada y se debe proporcionar una implementación más adecuada mediante la implementación de clases. ejemplo final

19.4.4 Propiedades de la interfaz

Esta cláusula aumenta la descripción de las propiedades de las clases §15.7 para las propiedades declaradas en interfaces.

Las propiedades de interfaz se declaran mediante property_declarations (§15.7.1) con las siguientes reglas adicionales:

  • property_modifier no overrideincluirá .

  • Una implementación explícita del miembro de interfaz no contendrá una accessor_modifier (§15.7.3).

  • Una interfaz derivada puede implementar explícitamente una propiedad de interfaz abstracta declarada en una interfaz base.

    Nota: Como una interfaz no puede contener campos de instancia, una propiedad de interfaz no puede ser una propiedad automática de instancia, ya que requeriría la declaración de campos de instancia ocultos implícitos. nota final

  • El tipo de una propiedad de interfaz será seguro para la salida si hay un descriptor de acceso get y será seguro para la entrada si hay un descriptor de acceso set.

  • Una declaración de método de interfaz que tiene un cuerpo de bloque o un cuerpo de expresión como un method_body es virtual; el virtual modificador no es necesario, pero se permite.

  • Una instancia property_declaration que no tiene ninguna implementación es abstract; el abstract modificador no es necesario, pero se permite. Nunca se considera una propiedad implementada automáticamente (§15.7.4).

19.4.5 Eventos de interfaz

Esta cláusula aumenta la descripción de los eventos en las clases §15.8 para los eventos declarados en interfaces.

Los eventos de interfaz se declaran mediante event_declarations (§15.8.1), con las siguientes reglas adicionales:

  • event_modifier no overrideincluirá .
  • Una interfaz derivada puede implementar un evento de interfaz abstracta declarado en una interfaz base (§15.8.5).
  • Se trata de un error en tiempo de compilación para variable_declarators en una instancia event_declaration contener cualquier variable_initializers.
  • Un evento de instancia con los virtual modificadores o sealed debe declarar descriptores de acceso. Nunca se considera un evento similar a un campo implementado automáticamente (§15.8.2).
  • Un evento de instancia con el abstract modificador no debe declarar descriptores de acceso.
  • El tipo de un evento de interfaz será seguro para la entrada.

Indexadores de interfaz 19.4.6

Esta cláusula aumenta la descripción de los indexadores en las clases §15.9 para los indexadores declarados en interfaces.

Los indexadores de interfaz se declaran mediante indexer_declarations (§15.9), con las siguientes reglas adicionales:

  • indexer_modifier no overrideincluirá .

  • Un indexer_declaration que tiene un cuerpo de expresión o contiene un descriptor de acceso con un cuerpo de bloque o cuerpo de expresión es virtual; el virtual modificador no es necesario, pero se permite.

  • Un indexer_declaration cuyos cuerpos de descriptor de acceso son punto y coma (;) es abstract; el abstract modificador no es necesario, pero se permite.

  • Todos los tipos de parámetro de un indexador de interfaz serán seguros para la entrada (§19.2.3.2).

  • Cualquier tipo de parámetro de salida o referencia también será seguro para la salida.

    Nota: Los parámetros de salida deben ser seguros para la entrada debido a restricciones comunes de implementación. nota final

  • El tipo de un indexador de interfaz será seguro para la salida si hay un descriptor de acceso get y será seguro para la entrada si hay un descriptor de acceso establecido.

19.4.7 Operadores de interfaz

Esta cláusula aumenta la descripción de operator_declaration miembros en las clases §15.10 para los operadores declarados en interfaces.

Un operator_declaration en una interfaz es la implementación (§19.1).

Es un error en tiempo de compilación para que una interfaz declare una conversión, igualdad o operador de desigualdad.

19.4.8 Constructores estáticos de interfaz

Esta cláusula aumenta la descripción de los constructores estáticos en las clases §15.12 para los constructores estáticos declarados en interfaces.

El constructor estático de una interfaz cerrada (§8.4.3) se ejecuta como máximo una vez en un dominio de aplicación determinado. La ejecución de un constructor estático se desencadena mediante la primera de las siguientes acciones que se deben producir dentro de un dominio de aplicación:

  • Se hace referencia a cualquiera de los miembros estáticos de la interfaz.
  • Antes de llamar al Main método para una interfaz que contiene el Main método (§7.1) en el que comienza la ejecución.
  • Esa interfaz proporciona una implementación para un miembro y se accede a esa implementación como la implementación más específica (§19.4.10) para ese miembro.

Nota: En el caso de que no se produzca ninguna de las acciones anteriores, es posible que el constructor estático de una interfaz no se ejecute para un programa en el que se creen y usen instancias de tipos que implementan la interfaz. nota final

Para inicializar un nuevo tipo de interfaz cerrada, primero se crea un nuevo conjunto de campos estáticos para ese tipo cerrado determinado. Cada uno de los campos estáticos se inicializa en su valor predeterminado. A continuación, los inicializadores de campo estáticos se ejecutan para esos campos estáticos. Por último, se ejecuta el constructor estático.

Nota: Consulte §19.4.2 para obtener un ejemplo de uso de varios tipos de miembros estáticos (incluido un método Main) declarado dentro de una interfaz. nota final

19.4.9 Tipos anidados de interfaz

Esta cláusula aumenta la descripción de los tipos anidados en las clases §15.3.9 para los tipos anidados declarados en interfaces.

Es un error declarar un tipo de clase, un tipo de estructura o un tipo de enumeración dentro del ámbito de un parámetro de tipo que se declaró con un variance_annotation (§19.2.3.1).

Ejemplo: La declaración de C abajo es un error.

interface IOuter<out T>
{
    class C { } // error: class declaration within scope of variant type parameter 'T'
}

ejemplo final

Implementación más específica de la versión 19.4.10

Cada clase y estructura tendrán una implementación más específica para cada miembro virtual declarado en todas las interfaces implementadas por ese tipo entre las implementaciones que aparecen en el tipo o sus interfaces directas e indirectas. La implementación más específica es una implementación única que es más específica que todas las demás.

Nota: La regla de implementación más específica garantiza que el programador resuelva explícitamente la ambigüedad derivada de la herencia de la interfaz de diamante en el momento en que se produce el conflicto. nota final

Para un tipo T que es una estructura o una clase que implementa interfaces I2 y I3, donde I2 y I3 derivan directa o indirectamente de la interfaz I que declara un miembro M, la implementación más específica de M es:

  • Si T declara una implementación de I.M, esa implementación es la implementación más específica.
  • De lo contrario, si T es una clase y una clase base directa o indirecta declara una implementación de I.M, la clase base más derivada de T es la implementación más específica.
  • De lo contrario, si I2 y son interfaces implementadas por I3 y T derivan de I3 directa o indirectamente, I2 es una implementación más específica que I3.MI2.M .
  • De lo contrario, ni I2.M son I3.M más específicos ni se produce un error.

Ejemplo:

interface IA
{
    void M() { Console.WriteLine("IA.M"); }
}

interface IB : IA
{
    void IA.M() { Console.WriteLine("IB.M"); }
}

interface IC: IA
{
    void IA.M() { Console.WriteLine("IC.M"); }
}

abstract class C: IB, IC { } // error: no most specific implementation for 'IA.M'

abstract class D: IA, IB, IC // OK
{
    public abstract void M();
}

La regla de implementación más específica garantiza que el programador resuelva explícitamente un conflicto (es decir, una ambigüedad derivada de la herencia de diamantes) en el momento en que surge el conflicto. ejemplo final

Acceso a miembros de la interfaz 19.4.11

Se accede a los miembros de la interfaz a través de expresiones de acceso a miembros (§12.8.7) e indexador (§12.8.12.4) del formulario I.M y I[A], donde I es un tipo de interfaz, es una constante, M campo, método, propiedad o evento de ese tipo de interfaz y A es una lista de argumentos del indexador.

En una clase D, con clase Bbase directa o indirecta, donde B implementa directamente o indirectamente la interfaz I y I define un método M(), la expresión base.M() solo es válida si base.M() estáticamente (§12.3) se enlaza a una implementación de en un tipo de M() clase.

Para las interfaces que son estrictamente de herencia única (cada interfaz de la cadena de herencia tiene exactamente cero o una interfaz base directa), los efectos de la búsqueda de miembros (§12.5), la invocación de método (§12.8.10.2) y el acceso al indexador (§12.8.12.4) son exactamente los mismos que para las clases y estructuras: más miembros derivados ocultan menos miembros derivados con el mismo nombre o firma. Sin embargo, en el caso de las interfaces de herencia múltiple, pueden producirse ambigüedades cuando dos o más interfaces base no relacionadas declaran miembros con el mismo nombre o firma. Esta subcláusula muestra varios ejemplos, algunos de los cuales conducen a ambigüedades y otros no. En todos los casos, se pueden usar conversiones explícitas para resolver las ambigüedades.

Example: En el código de ejemplo siguiente

interface IList
{
    int Count { get; set; }
}

interface ICounter
{
    int Count { get; set; }
}

interface IListCounter : IList, ICounter {}

class C
{
    void Test(IListCounter x)
    {
        x.Count = 1;             // Error
        ((IList)x).Count = 1;    // Ok, invokes IList.Count.set
        ((ICounter)x).Count = 1; // Ok, invokes ICounter.Count
    }
}

la primera declaración provoca un error en tiempo de compilación porque la búsqueda de miembros (§12.5) de Count en IListCounter es ambigua. Como se muestra en el ejemplo, la ambigüedad se resuelve mediante la conversión de x al tipo de interfaz base adecuado. Estas conversiones no tienen costos en tiempo de ejecución y simplemente consisten en ver la instancia como un tipo menos derivado en tiempo de compilación.

ejemplo final

Example: En el código de ejemplo siguiente

interface IInteger
{
    void Add(int i);
}

interface IDouble
{
    void Add(double d);
}

interface INumber : IInteger, IDouble {}

class C
{
    void Test(INumber n)
    {
        n.Add(1);             // Invokes IInteger.Add
        n.Add(1.0);           // Only IDouble.Add is applicable
        ((IInteger)n).Add(1); // Only IInteger.Add is a candidate
        ((IDouble)n).Add(1);  // Only IDouble.Add is a candidate
    }
}

la invocación n.Add(1) selecciona IInteger.Add aplicando reglas de resolución de sobrecarga de la sección 12.6.4. Del mismo modo, la invocación n.Add(1.0) selecciona IDouble.Add. Cuando se insertan conversiones explícitas, solo hay un método candidato y, por lo tanto, no hay ambigüedad.

ejemplo final

Example: En el código de ejemplo siguiente

interface IBase
{
    void F(int i);
}

interface ILeft : IBase
{
    new void F(int i);
}

interface IRight : IBase
{
    void G();
}

interface IDerived : ILeft, IRight {}

class A
{
    void Test(IDerived d)
    {
        d.F(1);           // Invokes ILeft.F
        ((IBase)d).F(1);  // Invokes IBase.F
        ((ILeft)d).F(1);  // Invokes ILeft.F
        ((IRight)d).F(1); // Invokes IBase.F
    }
}

el IBase.F miembro está oculto por el ILeft.F miembro. Por lo tanto, la invocación d.F(1) selecciona ILeft.F, aunque IBase.F parece no estar oculto en la ruta de acceso que conduce a través de IRight.

La regla intuitiva para ocultar en interfaces de herencia múltiple es simplemente esta: si un miembro está oculto en cualquier ruta de acceso, se oculta en todas las rutas de acceso. Dado que la ruta de acceso de IDerived a ILeft a IBase oculta IBase.F, el miembro también está oculto en la ruta de acceso de IDerived a IRight a IBase.

ejemplo final

19.5 Nombres de miembros de interfaz calificados

A veces, se refiere a un miembro de interfaz por su nombre de miembro de interfaz completo. El nombre completo de un miembro de interfaz consta del nombre de la interfaz en la que se declara el miembro, seguido de un punto, seguido del nombre del miembro. El nombre completo de un miembro hace referencia a la interfaz en la que se declara el miembro.

Ejemplo: Dadas las declaraciones

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

el nombre completo de Paint es IControl.Paint y el nombre completo de SetText es ITextBox.SetText. En el ejemplo anterior, no es posible hacer referencia a Paint como ITextBox.Paint.

ejemplo final

Cuando una interfaz forma parte de un espacio de nombres, un nombre de miembro de interfaz completo puede incluir el nombre del espacio de nombres.

Ejemplo:

namespace GraphicsLib
{
    interface IPolygon
    {
        void CalculateArea();
    }
}

Dentro del GraphicsLib espacio de nombres, ambos IPolygon.CalculateArea y GraphicsLib.IPolygon.CalculateArea son nombres calificados de miembro de interfaz para el método CalculateArea.

ejemplo final

Implementaciones de interfaz 19.6

19.6.1 General

Las interfaces se pueden implementar mediante clases y estructuras. Para indicar que una clase o estructura implementa directamente una interfaz, la interfaz se incluye en la lista de clases base de la clase o estructura.

Una clase o estructura C que implementa una interfaz I debe proporcionar o heredar una implementación para cada miembro declarado en I que C pueda acceder. Los miembros públicos de I se pueden definir en miembros públicos de C. Los miembros no públicos declarados en I que son accesibles pueden C definirse en C mediante la implementación de interfaz explícita (§19.6.2).

Un miembro de un tipo derivado que satisface la asignación de interfaz (§19.6.5), pero no implementa el miembro de interfaz base coincidente introduce un nuevo miembro. Esto ocurre cuando se requiere la implementación explícita de la interfaz para definir el miembro de interfaz.

Ejemplo:

interface ICloneable
{
    object Clone();
}

interface IComparable
{
    int CompareTo(object other);
}

class ListEntry : ICloneable, IComparable
{
    public object Clone() {...}    
    public int CompareTo(object other) {...}
}

ejemplo final

Una clase o estructura que implementa directamente una interfaz también implementa implícitamente todas las interfaces base de la interfaz. Esto es cierto incluso si la clase o estructura no enumera explícitamente todas las interfaces base de la lista de clases base.

Ejemplo:

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

class TextBox : ITextBox
{
    public void Paint() {...}
    public void SetText(string text) {...}
}

Aquí, la clase TextBox implementa IControl y ITextBox.

ejemplo final

Cuando una clase C implementa directamente una interfaz, todas las clases derivadas de C también implementan la interfaz implícitamente.

Las interfaces base especificadas en una declaración de clase se pueden construir tipos de interfaz (§8.4, §19.2).

Ejemplo: El código siguiente muestra cómo una clase puede implementar tipos de interfaz construida:

class C<U, V> {}
interface I1<V> {}
class D : C<string, int>, I1<string> {}
class E<T> : C<int, T>, I1<T> {}

ejemplo final

Las interfaces base de una declaración de clase genérica cumplirán la regla de unicidad descrita en §19.6.3.

19.6.2 Implementaciones explícitas de miembros de interfaz

Para implementar interfaces, una clase, estructura o interfaz puede declarar implementaciones explícitas de miembros de interfaz. Una implementación explícita de miembro de interfaz es una declaración de método, propiedad, evento o indexador que hace referencia a un nombre de miembro de interfaz completo. Una clase o estructura que implementa un miembro no público en una interfaz base debe declarar una implementación de miembro de interfaz explícita. Una interfaz que implementa un miembro en una interfaz base debe declarar una implementación explícita de miembro de interfaz.

Un miembro de interfaz derivado que satisface la asignación de interfaz (§19.6.5) oculta el miembro de la interfaz base (§7.7.2). El compilador emitirá una advertencia a menos que el new modificador esté presente.

Ejemplo:

interface IList<T>
{
    T[] GetElements();
}

interface IDictionary<K, V>
{
    V this[K key] { get; }
    void Add(K key, V value);
}

class List<T> : IList<T>, IDictionary<int, T>
{
    public T[] GetElements() {...}
    T IDictionary<int, T>.this[int index] {...}
    void IDictionary<int, T>.Add(int index, T value) {...}
}

Aquí, IDictionary<int,T>.this y IDictionary<int,T>.Add son implementaciones explícitas de miembros de interfaz.

ejemplo final

Ejemplo: En algunos casos, es posible que el nombre de un miembro de interfaz no sea adecuado para la clase de implementación, en cuyo caso, el miembro de interfaz se puede implementar mediante la implementación explícita del miembro de interfaz. Una clase que implementa una abstracción de archivos, por ejemplo, probablemente implementaría una función miembro Close que tenga el efecto de liberar el recurso de archivo e implementaría el método Dispose de la interfaz IDisposable mediante la implementación explícita del miembro de interfaz:

interface IDisposable
{
    void Dispose();
}

class MyFile : IDisposable
{
    void IDisposable.Dispose() => Close();

    public void Close()
    {
        // Do what's necessary to close the file
        System.GC.SuppressFinalize(this);
    }
}

ejemplo final

No es posible acceder a una implementación explícita de miembro de interfaz a través de su nombre de miembro de interfaz calificado en una invocación de método, acceso a propiedades, acceso a eventos o acceso al indexador. Solo se puede tener acceso a una implementación explícita de miembro de instancia de interfaz a través de una instancia de interfaz y, en ese caso, se hace referencia simplemente a ella por su nombre de miembro. Solo se puede tener acceso a una implementación de miembro estático de interfaz explícita a través del nombre de la interfaz.

Es un error en tiempo de compilación que una implementación de miembro de interfaz explícita incluya modificadores (sección 15.6) que no sean extern o async.

Una implementación explícita del método de interfaz hereda cualquier restricción de parámetro de tipo de la interfaz.

Un type_parameter_constraints_clause en una implementación explícita de un método de interfaz solo puede consistir en los classstructprimary_constraints aplicados a type_parameters que, según las restricciones heredadas, se conocen como tipos de referencia o de valor, respectivamente. Cualquier tipo del formulario T? en la firma de la implementación explícita del método de interfaz, donde T es un parámetro de tipo, se interpreta de la siguiente manera:

  • Si se agrega una class restricción para el parámetro de tipo T, entonces T? es un tipo de referencia anulable; de lo contrario,
  • Si no hay ninguna restricción agregada o se agrega una struct restricción para el parámetro T de tipo, entonces T? es un tipo de valor nulo.

Ejemplo: a continuación se muestra cómo funcionan las reglas cuando intervienen los parámetros de tipo:

#nullable enable
interface I
{
    void Foo<T>(T? value) where T : class;
    void Foo<T>(T? value) where T : struct;
}

class C : I
{
    void I.Foo<T>(T? value) where T : class { }
    void I.Foo<T>(T? value) where T : struct { }
}

Sin la restricción where T : classde parámetro de tipo , no se puede invalidar el método base con el parámetro de tipo de referencia. ejemplo final

Nota: Las implementaciones explícitas de miembros de interfaz tienen características de accesibilidad diferentes a otros miembros. Dado que las implementaciones explícitas de miembros de interfaz nunca son accesibles a través de un nombre de miembro de interfaz completo en una invocación de método o un acceso a propiedades, son en cierto modo privadas. Sin embargo, dado que se puede acceder a ellas a través de la interfaz, también son públicas, como la interfaz en la que se declaran. Las implementaciones explícitas de miembros de interfaz sirven para dos propósitos principales:

  • Dado que las implementaciones explícitas de miembros de interfaz no son accesibles a través de instancias de clase o estructura, permiten que las implementaciones de interfaz se excluyan de la interfaz pública de una clase o estructura. Esto resulta especialmente útil cuando una clase o estructura implementa una interfaz interna que no es de interés para un consumidor de esa clase o estructura.
  • Las implementaciones explícitas de miembros de interfaz permiten la desambiguación de los miembros de interfaz con la misma firma. Sin implementaciones explícitas de miembros de interfaz, sería imposible que una clase, estructura o interfaz tuviera implementaciones diferentes de miembros de interfaz con la misma firma y tipo de valor devuelto, ya que sería imposible que una clase, estructura o interfaz tuviera cualquier implementación en todos los miembros de interfaz con la misma firma, pero con diferentes tipos de valor devuelto.

nota final

Para que una implementación explícita de miembro de interfaz sea válida, la clase, la estructura o la interfaz denominarán una interfaz en su lista de interfaz base o clase base que contenga un miembro cuyo nombre de miembro de interfaz calificado, tipo, número de parámetros de tipo y tipos de parámetro coincidan exactamente con los de la implementación explícita del miembro de interfaz. Si un miembro de función de interfaz tiene una matriz de parámetros, se permite que el parámetro correspondiente de una implementación de miembro de interfaz explícita asociada, pero no es necesario, que tenga el modificador params. Si el miembro de la función de interfaz no tiene una matriz de parámetros, una implementación de miembro de interfaz explícita asociada no tendrá una matriz de parámetros.

Ejemplo: Por lo tanto, en la siguiente clase

class Shape : ICloneable
{
    object ICloneable.Clone() {...}
    int IComparable.CompareTo(object other) {...} // invalid
}

la declaración de IComparable.CompareTo da como resultado un error en tiempo de compilación porque IComparable no aparece en la lista de clases base de Shape y no es una interfaz base de ICloneable. Del mismo modo, en las declaraciones

class Shape : ICloneable
{
    object ICloneable.Clone() {...}
}

class Ellipse : Shape
{
    object ICloneable.Clone() {...} // invalid
}

la declaración de ICloneable.Clone en Ellipse da como resultado un error en tiempo de compilación porque ICloneable no aparece explícitamente en la lista de clases base de Ellipse.

ejemplo final

El nombre del miembro de interfaz calificado de una implementación explícita del miembro de interfaz hará referencia a la interfaz en la que se declaró el miembro.

Ejemplo: Por lo tanto, en las declaraciones

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

class TextBox : ITextBox
{
    void IControl.Paint() {...}
    void ITextBox.SetText(string text) {...}
}

La implementación explícita del miembro de interfaz de Paint debe escribirse como IControl.Paint, no como ITextBox.Paint.

ejemplo final

19.6.3 Unicidad de interfaces implementadas

Las interfaces implementadas por una declaración de tipo genérico seguirán siendo únicas para todos los tipos construidos posibles. Sin esta regla, sería imposible determinar el método correcto para llamar a determinados tipos construidos.

Ejemplo: Supongamos que se permitió escribir una declaración de clase genérica de la siguiente manera:

interface I<T>
{
    void F();
}

class X<U ,V> : I<U>, I<V> // Error: I<U> and I<V> conflict
{
    void I<U>.F() {...}
    void I<V>.F() {...}
}

Si esto fuera permitido, sería imposible determinar qué código ejecutar en el siguiente caso:

I<int> x = new X<int, int>();
x.F();

ejemplo final

Para determinar si la lista de interfaces de una declaración de tipo genérico es válida, se realizan los pasos siguientes:

  • Supongamos que L es la lista de interfaces especificadas directamente en una clase genérica, estructura o declaración de interfaz C.
  • Agregue a L cualquier interfaz base de las interfaces que ya estén en L.
  • Quite los duplicados de L.
  • Si en cualquier tipo construido posible creado a partir de C, después de que los argumentos de tipo se sustituyan en L, dos interfaces en L son idénticas, la declaración de C no es válida. Las declaraciones de restricciones no se tienen en cuenta al determinar todos los tipos construidos posibles.

Nota: En la declaración de clase X anterior, la lista de interfaz L consta de l<U> y I<V>. La declaración no es válida porque cualquier tipo construido con U y V del mismo tipo haría que estas dos interfaces fueran de tipos idénticos. nota final

Es posible que las interfaces especificadas en diferentes niveles de herencia se unifiquen:

interface I<T>
{
    void F();
}

class Base<U> : I<U>
{
    void I<U>.F() {...}
}

class Derived<U, V> : Base<U>, I<V> // Ok
{
    void I<V>.F() {...}
}

Este código es válido aunque Derived<U,V> implemente I<U> y I<V>. El código.

I<int> x = new Derived<int, int>();
x.F();

invoca el método en Derived, ya que Derived<int,int>' vuelve a implementar I<int> de forma eficaz (§19.6.7).

19.6.4 Implementación de métodos genéricos

Cuando un método genérico implementa implícitamente un método de interfaz, las restricciones dadas para cada parámetro de tipo de método serán equivalentes en ambas declaraciones (después de reemplazar cualquier parámetro de tipo de interfaz por los argumentos de tipo apropiados), donde los parámetros de tipo de método se identifican mediante posiciones ordinales, de izquierda a derecha.

Ejemplo: En el código siguiente:

interface I<X, Y, Z>
{
    void F<T>(T t) where T : X;
    void G<T>(T t) where T : Y;
    void H<T>(T t) where T : Z;
}

class C : I<object, C, string>
{
    public void F<T>(T t) {...}                  // Ok
    public void G<T>(T t) where T : C {...}      // Ok
    public void H<T>(T t) where T : string {...} // Error
}

el método C.F<T> implementa de manera explícita I<object,C,string>.F<T>. En este caso, C.F<T> no es necesario (ni permitido) especificar la restricción T: object , ya que object es una restricción implícita en todos los parámetros de tipo. El método C.G<T> implementa implícitamente I<object,C,string>.G<T> porque las restricciones coinciden con las de la interfaz, después de reemplazar los parámetros de tipo de interfaz por los argumentos de tipo correspondientes. La restricción para el método C.H<T> es un error porque los tipos sellados (string en este caso) no se pueden usar como restricciones. Omitir la restricción también sería un error, ya que las restricciones de implementaciones implícitas del método de interfaz deben coincidir. Por lo tanto, es imposible implementar implícitamente I<object,C,string>.H<T>. Este método de interfaz solo se puede implementar mediante una implementación explícita de miembro de interfaz:

class C : I<object, C, string>
{
    ...
    public void H<U>(U u) where U : class {...}

    void I<object, C, string>.H<T>(T t)
    {
        string s = t; // Ok
        H<T>(t);
    }
}

En este caso, la implementación explícita del miembro de interfaz invoca un método público que tiene restricciones estrictamente más débiles. La asignación de t a s es válida, ya que T hereda una restricción de T: string, aunque esta restricción no se pueda expresar en el código fuente. ejemplo final

Nota: Cuando un método genérico implementa explícitamente un método de interfaz no se permiten restricciones en el método de implementación (§15.7.1, §19.6.2). nota final

Asignación de interfaz 19.6.5

Una clase o estructura proporcionará implementaciones de todos los miembros abstractos de las interfaces que se enumeran en la lista de clases base de la clase o estructura. El proceso de buscar implementaciones de miembros de interfaz en una clase o estructura de implementación se conoce como asignación de interfaz.

La asignación de interfaz para una clase o estructura C busca una implementación para cada miembro de cada interfaz especificada en la lista de clases base de C. La implementación de un miembro I.Mde interfaz determinado , donde I es la interfaz en la que se declara el miembro M , se determina examinando cada clase, interfaz o estructura S, empezando por C y repitiendo para cada clase base sucesiva e interfaz implementada de C, hasta que se encuentre una coincidencia:

  • Si S contiene una declaración de una implementación de miembro de interfaz explícita que coincide con I y M, este miembro es la implementación de I.M.
  • De lo contrario, si S contiene una declaración de un miembro público no estático que coincide con M, este miembro es la implementación de I.M. Si más de un miembro coincide, no se especifica qué miembro es la implementación de I.M. Esta situación solo puede producirse si S es un tipo construido en el que los dos miembros declarados en el tipo genérico tienen firmas diferentes, pero los argumentos de tipo hacen que sus firmas sean idénticas.

Se produce un error en tiempo de compilación si no se pueden encontrar implementaciones para todos los miembros de todas las interfaces especificadas en la lista de clases base de C. Los miembros de una interfaz incluyen los miembros que se heredan de las interfaces base.

Los miembros de un tipo de interfaz construido se considera que los parámetros de tipo han sido reemplazados por los argumentos de tipo correspondientes, tal como se especifica en §15.3.3.

Ejemplo: Por ejemplo, dada la declaración de interfaz genérica:

interface I<T>
{
    T F(int x, T[,] y);
    T this[int y] { get; }
}

la interfaz construida I<string[]> tiene los miembros:

string[] F(int x, string[,][] y);
string[] this[int y] { get; }

ejemplo final

Con fines de asignación de interfaz, un miembro A de clase, interfaz o estructura coincide con un miembro B de interfaz cuando:

  • A y B son métodos y el nombre, tipo y listas de parámetros de A y B son idénticos.
  • A y B son propiedades, el nombre y el tipo de A y B son idénticos, y A tiene los mismos descriptores de acceso que B (A se permite que tenga descriptores de acceso adicionales si no es una implementación explícita de un miembro de la interfaz).
  • A y B son eventos, y el nombre y tipo de A y B son idénticos.
  • A y B son indexadores, el tipo y las listas de parámetros de A y B son idénticos y A tiene los mismos descriptores de acceso, ya que B (A se permite que tenga descriptores de acceso adicionales si no es una implementación de miembro de interfaz explícita).

Las implicaciones importantes del algoritmo de asignación de interfaz son:

  • Las implementaciones explícitas de miembros de interfaz tienen prioridad sobre otros miembros de la misma clase o estructura al determinar la clase o el miembro de estructura que implementa un miembro de interfaz.
  • Ni los miembros no públicos ni los estáticos participan en la asignación de interfaz.

Example: En el código de ejemplo siguiente

interface ICloneable
{
    object Clone();
}

class C : ICloneable
{
    object ICloneable.Clone() {...}
    public object Clone() {...}
}

el miembro ICloneable.Clone de C actúa como la implementación de Clone en ICloneable porque las implementaciones explícitas de miembros de interfaz tienen prioridad sobre otros miembros.

ejemplo final

Si una clase o estructura implementa dos o más interfaces que contienen un miembro con el mismo nombre, tipo y tipos de parámetros, es posible asignar cada uno de esos miembros de interfaz a un solo miembro de clase o estructura.

Ejemplo:

interface IControl
{
    void Paint();
}

interface IForm
{
    void Paint();
}

class Page : IControl, IForm
{
    public void Paint() {...}
}

Aquí, los métodos Paint tanto de IControl como de IForm se asignan al método Paint en Page. Por supuesto, también es posible tener implementaciones de miembros de interfaz explícitas independientes para los dos métodos.

ejemplo final

Si una clase o estructura implementa una interfaz que contiene miembros ocultos, es posible que algunos miembros deban implementarse a través de implementaciones explícitas de miembros de interfaz.

Ejemplo:

interface IBase
{
    int P { get; }
}

interface IDerived : IBase
{
    new int P();
}

Una implementación de esta interfaz requeriría al menos una implementación explícita de miembro de interfaz y tomaría una de las siguientes formas

class C1 : IDerived
{
    int IBase.P { get; }
    int IDerived.P() {...}
}
class C2 : IDerived
{
    public int P { get; }
    int IDerived.P() {...}
}
class C3 : IDerived
{
    int IBase.P { get; }
    public int P() {...}
}

ejemplo final

Cuando una clase implementa varias interfaces que tienen la misma interfaz base, solo puede haber una implementación de la interfaz base.

Example: En el código de ejemplo siguiente

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

interface IListBox : IControl
{
    void SetItems(string[] items);
}

class ComboBox : IControl, ITextBox, IListBox
{
    void IControl.Paint() {...}
    void ITextBox.SetText(string text) {...}
    void IListBox.SetItems(string[] items) {...}
}

no es posible tener implementaciones independientes para el IControl denominado en la lista de clases base, el IControl heredado por ITextBox y el IControl heredado por IListBox. De hecho, no hay ninguna noción de una identidad independiente para estas interfaces. En su lugar, las implementaciones de ITextBoxy IListBox comparten la misma implementación de IControl y ComboBox simplemente se considera que implementa tres interfaces, IControl, ITextBox y IListBox.

ejemplo final

Los miembros de una clase base participan en la asignación de interfaz.

Example: En el código de ejemplo siguiente

interface Interface1
{
    void F();
}

class Class1
{
    public void F() {}
    public void G() {}
}

class Class2 : Class1, Interface1
{
    public new void G() {}
}

el método F de Class1 se usa en Class2's implementación de Interface1.

ejemplo final

Herencia de implementación de la interfaz 19.6.6

Una clase hereda todas las implementaciones de interfaz proporcionadas por sus clases base.

Sin volver a implementar explícitamente una interfaz, una clase derivada no puede modificar de ninguna manera las asignaciones de interfaz que hereda de sus clases base.

Ejemplo: En las declaraciones

interface IControl
{
    void Paint();
}

class Control : IControl
{
    public void Paint() {...}
}

class TextBox : Control
{
    public new void Paint() {...}
}

el método Paint en TextBox oculta el método Paint en Control, pero no modifica la asignación de Control.Paint en IControl.Paint, y las llamadas a Paint a través de instancias de clase e instancias de interfaz tendrán los siguientes efectos

Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint();  // invokes Control.Paint();
t.Paint();  // invokes TextBox.Paint();
ic.Paint(); // invokes Control.Paint();
it.Paint(); // invokes Control.Paint();

ejemplo final

Sin embargo, cuando un método de interfaz se asigna a un método virtual en una clase, es posible que las clases derivadas invaliden el método virtual y modifiquen la implementación de la interfaz.

Ejemplo: Reescritura de las declaraciones anteriores a

interface IControl
{
    void Paint();
}

class Control : IControl
{
    public virtual void Paint() {...}
}

class TextBox : Control
{
    public override void Paint() {...}
}

ahora se observarán los siguientes efectos

Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint();  // invokes Control.Paint();
t.Paint();  // invokes TextBox.Paint();
ic.Paint(); // invokes Control.Paint();
it.Paint(); // invokes TextBox.Paint();

ejemplo final

Dado que las implementaciones explícitas de miembros de interfaz no se pueden declarar virtuales, no es posible invalidar una implementación de miembro de interfaz explícita. Sin embargo, es perfectamente válido que una implementación de miembro de interfaz explícita llame a otro método y que otro método se pueda declarar virtual para permitir que las clases derivadas lo invaliden.

Ejemplo:

interface IControl
{
    void Paint();
}

class Control : IControl
{
    void IControl.Paint() { PaintControl(); }
    protected virtual void PaintControl() {...}
}

class TextBox : Control
{
    protected override void PaintControl() {...}
}

Aquí, las clases derivadas de Control pueden especializar la implementación de IControl.Paint invalidando el método PaintControl.

ejemplo final

19.6.7 Implementación de la interfaz

Una clase que hereda una implementación de interfaz puede volver a implementar la interfaz incluyiéndola en la lista de clases base.

Una nueva implementación de una interfaz sigue exactamente las mismas reglas de asignación de interfaz que una implementación inicial de una interfaz. Por lo tanto, la asignación de interfaz heredada no tiene ningún efecto en la asignación de interfaz establecida para la nueva implementación de la interfaz.

Ejemplo: En las declaraciones

interface IControl
{
    void Paint();
}

class Control : IControl
{
    void IControl.Paint() {...}
}

class MyControl : Control, IControl
{
    public void Paint() {}
}

el hecho de que Control se asigne IControl.Paint a Control.IControl.Paint no afecta a la nueva implementación en MyControl, que asigna IControl.Paint a MyControl.Paint.

ejemplo final

Las declaraciones de miembros públicos heredados y las declaraciones de miembros de interfaz explícita heredada participan en el proceso de mapeo de interfaz para las interfaces reimplementadas.

Ejemplo:

interface IMethods
{
    void F();
    void G();
    void H();
    void I();
}

class Base : IMethods
{
    void IMethods.F() {}
    void IMethods.G() {}
    public void H() {}
    public void I() {}
}

class Derived : Base, IMethods
{
    public void F() {}
    void IMethods.H() {}
}

Aquí, la implementación de IMethods en Derived asigna los métodos de interfaz a Derived.F, Base.IMethods.G, Derived.IMethods.H y Base.I.

ejemplo final

Cuando una clase implementa una interfaz, también implementa implícitamente todas las interfaces base de esa interfaz. Del mismo modo, una nueva implementación de una interfaz también es implícitamente una nueva implementación de todas las interfaces base de la interfaz.

Ejemplo:

interface IBase
{
    void F();
}

interface IDerived : IBase
{
    void G();
}

class C : IDerived
{
    void IBase.F() {...}
    void IDerived.G() {...}
}

class D : C, IDerived
{
    public void F() {...}
    public void G() {...}
}

Aquí, la nueva implementación de IDerived también vuelve a implementar IBase asignando IBase.F a D.F.

ejemplo final

19.6.8 Clases e interfaces abstractas

Al igual que una clase no abstracta, una clase abstracta proporcionará implementaciones de todos los miembros abstractos de las interfaces que se enumeran en la lista de clases base de la clase. Sin embargo, se permite que una clase abstracta asigne métodos de interfaz a métodos abstractos.

Ejemplo:

interface IMethods
{
    void F();
    void G();
}

abstract class C : IMethods
{
    public abstract void F();
    public abstract void G();
    }

Aquí, la implementación de IMethods mapea F y G en los métodos abstractos, que se sobrescribirán en clases no abstractas que deriven de C.

ejemplo final

Las implementaciones explícitas de miembros de interfaz no pueden ser abstractas, pero las implementaciones explícitas de miembros de interfaz pueden llamar a métodos abstractos.

Ejemplo:

interface IMethods
{
    void F();
    void G();
}

abstract class C: IMethods
{
    void IMethods.F() { FF(); }
    void IMethods.G() { GG(); }
    protected abstract void FF();
    protected abstract void GG();
}

Aquí, las clases no abstractas que derivan de C serían necesarias para invalidar FF y GG, lo que proporciona la implementación real de IMethods.

ejemplo final