Compartir a través de


18 Interfaces

18.1 General

Una interfaz define un contrato. Una clase o estructura que implemente una interfaz se adhiere a su contrato. Una interfaz puede heredar de varias interfaces base y una clase o estructura puede implementar varias interfaces.

Las interfaces pueden contener métodos, propiedades, eventos e indizadores. La propia interfaz no proporciona implementaciones para los miembros que declara. La interfaz simplemente especifica los miembros proporcionados por clases o estructuras que implementan la interfaz.

18.2 Declaraciones de interfaz

18.2.1 General

Un interface_declaration es un type_declaration (§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 (§22), seguido de un conjunto opcional de interface_modifiers (§18.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 variant_type_parameter_list (§18.2.3), seguida de un interface_base opcional especificación (§18.2.4), seguida de una especificación opcional de type_parameter_constraints_clauses (§15.2.5), seguida de un interface_body (§18.3), seguida opcionalmente de un punto y coma.

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

Una declaración de interfaz que proporciona un 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 (§8.4).

Modificadores de interfaz 18.2.2

Un 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 (§23.2) solo está disponible en código no seguro (§23).

Es un error en tiempo de compilación para que el mismo modificador aparezca varias veces en una declaración de interfaz.

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

Los publicmodificadores , protected, internaly 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 (§7.5.2). Cuando una declaración de tipo parcial (§15.2.7) incluye una especificación de accesibilidad (a través de los publicmodificadores , protected, internaly private ), se aplican las reglas de §15.2.2.

18.2.3 Listas de parámetros de tipo variant

18.2.3.1 General

Las listas de parámetros de tipo variant solo pueden producirse en tipos de interfaz y delegado. La diferencia entre los type_parameter_list normales es el variance_annotation opcional en cada parámetro detipo.

variant_type_parameter_list
    : '<' variant_type_parameters '>'
    ;
variant_type_parameters
    : attributes? variance_annotation? type_parameter
    | variant_type_parameters ',' 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 lo 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.

18.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 no es seguro para la salida si una de las siguientes suspensiones:

  • T es un parámetro de tipo contravariante.
  • T es un tipo de matriz con un tipo de elemento no seguro de 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 de entrada.

Un tipo T no es seguro de entrada si una de las siguientes suspensiones:

  • T es un parámetro de tipo covariante.
  • T es un tipo de matriz con un tipo de elemento no seguro de 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 de entrada.
    • Xᵢ es contravariante o invariable y Aᵢ no es seguro de salida.

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

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

18.2.3.3 Conversión de varianza

El propósito de las anotaciones de varianza es proporcionar conversiones más lencientes (pero todavía seguras para tipos) en tipos de interfaz y delegado. Para ello, las definiciones de las conversiones implícitas (§10.2) y explícitas (§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 una interfaz o un tipo delegado declarado con los parámetros de tipo variant y para cada parámetro Xᵢ de tipo variant una de las siguientes suspensionesT<Xᵢ, ..., Xᵥ>:

  • 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ᵢ

18.2.4 Interfaces base

Una interfaz puede heredar de cero o más tipos de interfaz, que se denominan interfaces base explícitas de 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.

interface_base
    : ':' interface_type_list
    ;

Las interfaces base explícitas se pueden construir tipos de interfaz (§8.4, §18.2). Una interfaz base no puede ser un parámetro de tipo propio, 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 serán al menos tan accesibles como la propia interfaz (§7.5.5).

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

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

Las interfaces base de 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, sus interfaces base explícitas, etc. Una interfaz hereda todos los miembros de sus interfaces base.

Ejemplo: en el código 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, ITextBoxy IListBox. En otras palabras, la IComboBox interfaz anterior hereda 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, los tipos constituyentes del miembro tienen los parámetros de tipo de la declaración de clase base reemplazados por los argumentos de tipo correspondientes usados en la especificación de class_base .

Ejemplo: en el código 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 Combine método después de reemplazar el parámetro T de tipo 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 (§15.2.7) se describe más adelante en §15.2.4.3.

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

Cuerpo de la interfaz 18.3

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

interface_body
    : '{' interface_member_declaration* '}'
    ;

18.4 Miembros de la interfaz

18.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
    : interface_method_declaration
    | interface_property_declaration
    | interface_event_declaration
    | interface_indexer_declaration
    ;

Una declaración de interfaz declara cero o más miembros. Los miembros de una interfaz deben ser métodos, propiedades, eventos o indizadores. Una interfaz no puede contener constantes, campos, operadores, constructores de instancia, finalizadores o tipos, ni puede una interfaz contener miembros estáticos de ningún tipo.

Todos los miembros de la interfaz tienen implícitamente acceso público. Se trata de un error en tiempo de compilación para que las declaraciones de miembro de interfaz incluyan los modificadores.

Un interface_declaration crea un nuevo espacio de declaración (§7.3) y los parámetros de tipo y interface_member_declarationque contiene inmediatamente el interface_declaration introducen nuevos miembros en este espacio de declaración. Las reglas siguientes se aplican a interface_member_declarations:

  • El nombre de un parámetro de tipo en el variant_type_parameter_list de una declaración de interfaz diferirá de los nombres de todos los demás parámetros de tipo de la misma variant_type_parameter_list y diferirá de los nombres de todos los miembros de la interfaz.
  • El nombre de un método debe diferir de los nombres de todas las propiedades y eventos declarados en la misma interfaz. Además, la firma (§7.6) de un método diferirá de las firmas de todos los demás métodos declarados en la misma interfaz y dos métodos declarados en la misma interfaz no tendrán firmas que difieren únicamente por in, outy ref.
  • El nombre de una propiedad o evento diferirá de los nombres de todos los demás miembros declarados en la misma interfaz.
  • La firma de un indexador diferirá de las firmas de todos los demás indizadores declarados en la misma interfaz.

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 hace que el compilador emita una advertencia. Para suprimir la advertencia, la declaración del miembro de interfaz derivada incluirá un new modificador para indicar que el miembro derivado está pensado para ocultar el miembro base. Este tema se describe más adelante en §7.7.2.3.

Si un new modificador se incluye en una declaración que no oculta un miembro heredado, se emite una advertencia a ese efecto. Esta advertencia se suprime quitando el new modificador .

Nota: Los miembros de la clase object no son, estrictamente hablando, miembros de ninguna interfaz (§18.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 (§12.5). nota final

El conjunto de miembros de una interfaz declarada en varias partes (§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 (§7.3) y el ámbito de cada miembro (§7.7) se extiende a los cuerpos de todas las partes.

Métodos de interfaz 18.4.2

Los métodos de interfaz se declaran mediante interface_method_declarations:

interface_method_declaration
    : attributes? 'new'? return_type interface_method_header
    | attributes? 'new'? ref_kind ref_return_type interface_method_header
    ;

interface_method_header
    : identifier '(' parameter_list? ')' ';'
    | identifier type_parameter_list '(' parameter_list? ')'
      type_parameter_constraints_clause* ';'
    ;

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 (§15.6). No se permite que una declaración de método de interfaz especifique un cuerpo del método y, por tanto, la declaración siempre termina con un punto y coma.

Todos los tipos de parámetros de un método de interfaz serán seguros para la entrada (§18.2.3.2) y el tipo de valor devuelto será void o seguro para la salida. Además, 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

Además, 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.

Además, 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 typesafe.

Ejemplo:

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

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

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

class B {}
class D : B {}
class E : B {}
class C : I<D>
{
    public void M<U>() {...} 
}

...

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

En realidad, se trata de una llamada a C.M<E>. Pero esa llamada requiere que E derive de D, por lo que la seguridad de tipos se infringiría aquí.

ejemplo final

18.4.3 Propiedades de la interfaz

Las propiedades de interfaz se declaran mediante interface_property_declarations:

interface_property_declaration
    : attributes? 'new'? type identifier '{' interface_accessors '}'
    | attributes? 'new'? ref_kind type identifier '{' ref_interface_accessor '}'
    ;

interface_accessors
    : attributes? 'get' ';'
    | attributes? 'set' ';'
    | attributes? 'get' ';' attributes? 'set' ';'
    | attributes? 'set' ';' attributes? 'get' ';'
    ;

ref_interface_accessor
    : attributes? 'get' ';'
    ;

Los atributos, el tipo y el identificador de una declaración de propiedad de interfaz tienen el mismo significado que los de una declaración de propiedad en una clase (§15.7).

Los descriptores de acceso de una declaración de propiedad de interfaz corresponden a los descriptores de acceso de una declaración de propiedad de clase (§15.7.3), salvo que el accessor_body siempre será un punto y coma. Por lo tanto, los descriptores de acceso simplemente indican si la propiedad es de solo lectura, de solo lectura o de solo escritura.

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.

18.4.4 Eventos de interfaz

Los eventos de interfaz se declaran mediante interface_event_declarations:

interface_event_declaration
    : attributes? 'new'? 'event' type identifier ';'
    ;

Los atributos, el tipo y el identificador de una declaración de evento de interfaz tienen el mismo significado que los de una declaración de evento en una clase (§15.8).

El tipo de un evento de interfaz será seguro para la entrada.

Indexadores de interfaz 18.4.5

Los indexadores de interfaz se declaran mediante interface_indexer_declarations:

interface_indexer_declaration
    : attributes? 'new'? type 'this' '[' parameter_list ']'
      '{' interface_accessors '}'
    | attributes? 'new'? ref_kind type 'this' '[' parameter_list ']'
      '{' ref_interface_accessor '}'
    ;

Los atributos, el tipo y parameter_list de una declaración del indexador de interfaz tienen el mismo significado que los de una declaración de indexador en una clase (§15.9).

Los descriptores de acceso de una declaración del indexador de interfaz corresponden a los descriptores de acceso de una declaración de indexador de clase (§15.9), salvo que el accessor_body siempre será un punto y coma. Por lo tanto, los descriptores de acceso simplemente indican si el indexador es de lectura y escritura, de solo lectura o de solo escritura.

Todos los tipos de parámetro de un indexador de interfaz serán seguros para la entrada (§18.2.3.2). Además, 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 indizador 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.

Acceso a miembros de interfaz 18.4.6

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

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.9.2) y el acceso al indexador (§12.8.11.3) 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, las ambigüedades pueden producirse cuando dos o más interfaces base no relacionadas declaran miembros con el mismo nombre o firma. Esta subclausa muestra varios ejemplos, algunos de los cuales conducen a ambigüedades y otros que no lo hacen. En todos los casos, las conversiones explícitas se pueden usar para resolver las ambigüedades.

Ejemplo: en el código siguiente

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

interface ICounter
{
    void Count(int i);
}

interface IListCounter : IList, ICounter {}

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

las dos primeras instrucciones provocan errores 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 x al tipo de interfaz base adecuado. Estas conversiones no tienen costos en tiempo de ejecución, sino que simplemente consisten en ver la instancia como un tipo menos derivado en tiempo de compilación.

ejemplo final

Ejemplo: en el código 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 §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 tanto, no hay ambigüedad.

ejemplo final

Ejemplo: en el código 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 oculta en la ruta de acceso que conduce a través de IRight.

La regla intuitiva para ocultarse en interfaces de herencia múltiple es simplemente esto: 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 oculta IBase.FILeft IBase , el miembro también está oculto en la ruta de acceso de IDerived a IBaseIRight .

ejemplo final

18.5 Nombres de miembros de interfaz calificados

A veces, su nombre de miembro de interfaz calificado hace referencia a un miembro de interfaz. 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 calificado puede incluir el nombre del espacio de nombres.

Ejemplo:

namespace System
{
    public interface ICloneable
    {
        object Clone();
    }
}

Dentro del System espacio de nombres , y System.ICloneable.Clone ICloneable.Clone son nombres de miembro de interfaz calificados para el Clone método .

ejemplo final

Implementaciones de interfaz 18.6

18.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.

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 y IControl 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, §18.2).

Ejemplo: el código siguiente muestra cómo una clase puede implementar tipos de interfaz construidos:

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 §18.6.3.

18.6.2 Implementaciones explícitas de miembros de interfaz

Para la implementación de interfaces, una clase o estructura puede declarar implementaciones explícitas de miembros de interfaz. Una implementación de miembro de interfaz explícita es una declaración de método, propiedad, evento o indexador que hace referencia a un nombre de miembro de interfaz calificado.

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 Close función miembro que tenga el efecto de liberar el recurso de archivo e implementaría el Dispose método de la interfaz mediante la IDisposable 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 interfaz a través de una instancia de interfaz y, en ese caso, se hace referencia a ella simplemente por su nombre de miembro.

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

Se trata de un error en tiempo de compilación para que una implementación explícita del método de interfaz incluya type_parameter_constraints_clauses. Las restricciones de una implementación genérica de método de interfaz explícita se heredan del método de interfaz.

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 calificado en una invocación de método o un acceso a propiedades, se encuentran en un sentido privado. Sin embargo, dado que se puede acceder a ellos a través de la interfaz, también son públicos 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 las implementaciones explícitas de miembros de interfaz sería imposible que una clase o estructura tuviera diferentes implementaciones de miembros de interfaz con la misma firma y tipo de valor devuelto, ya que sería imposible que una clase o estructura tuviera ninguna implementación en todos los miembros de interfaz con la misma firma, pero con tipos de valor devuelto diferentes.

nota final

Para que una implementación explícita de miembro de interfaz sea válida, la clase o estructura asignará un nombre a una interfaz en su lista de clases 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 el parámetro correspondiente de una implementación de miembro de interfaz explícita asociada, pero no necesaria, para tener el params modificador . 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 da como resultado un error en tiempo de IComparable.CompareTo 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 en Ellipse da como resultado un error en tiempo de ICloneable.Clone 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 ITextBox.Paintcomo .

ejemplo final

18.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 interfaz de una declaración de tipo genérico es válida, se realizan los pasos siguientes:

  • Vamos L a ser la lista de interfaces especificadas directamente en una clase genérica, estructura o declaración Cde interfaz .
  • Agregue a L cualquier interfaz base de las interfaces que ya estén en L.
  • Quite los duplicados de L.
  • Si cualquier tipo construido posible creado a partir C de lo haría, después de que los argumentos de tipo se sustituyan en L, haga que dos interfaces en L sean 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 X de clase anterior, la lista L de interfaz consta de l<U> y I<V>. La declaración no es válida porque cualquier tipo construido con U y V que sea el mismo tipo haría que estas dos interfaces fueran tipos idénticos. nota final

Es posible que las interfaces especificadas en diferentes niveles de herencia 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 y I<U> 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 (§18.6.7).

18.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 I<object,C,string>.F<T>implícitamente . 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 I<object,C,string>.G<T> implícitamente 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 se requieren restricciones de implementaciones implícitas del método de interfaz para que coincidan. Por lo tanto, es imposible implementar I<object,C,string>.H<T>implícitamente . 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, §18.6.2). nota final

Asignación de interfaz 18.6.5

Una clase o estructura proporcionará implementaciones de todos los miembros 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 o estructura S, empezando por C y repitiendo para cada clase base sucesiva 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 I con 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 Mcon , 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 las implementaciones no se pueden encontrar 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 consideran que los parámetros de tipo se reemplazan 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 I<string[]> construida 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 o estructura coincide con un miembro B de interfaz cuando:

  • A y B son métodos, y el nombre, el tipo y las 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 tienen los mismos descriptores de acceso que B (A se permite tener descriptores de acceso adicionales si no es una implementación explícita de miembro de interfaz).
  • A y B son eventos, y el nombre y el 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 tienen los mismos descriptores de acceso que B (A se permite tener descriptores de acceso adicionales si no es una implementación explícita de miembro de interfaz).

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 estáticos participan en la asignación de interfaz.

Ejemplo: en el código siguiente

interface ICloneable
{
    object Clone();
}

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

el ICloneable.Clone miembro de C se convierte en la implementación de Clone en "ICloneable" porque las implementaciones explícitas del miembro 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 una sola clase o miembro de estructura.

Ejemplo:

interface IControl
{
    void Paint();
}

interface IForm
{
    void Paint();
}

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

Aquí, los Paint métodos de y IForm IControl se asignan al Paint método en Page. Por supuesto, también es posible tener implementaciones de miembros de interfaz explícitos 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 deba 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.

Ejemplo: en el código 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 objeto denominado en la lista de clases base, el IControl heredado por ITextBoxy 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 IControly ComboBox simplemente se consideran para implementar tres interfaces, IControl, ITextBoxy IListBox.

ejemplo final

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

Ejemplo: en el código 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 la Class2's implementación de Interface1.

ejemplo final

Herencia de implementación de interfaz 18.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 Paint método en TextBox oculta el Paint método en Control, pero no modifica la asignación de Control.Paint en IControl.Paint, y las llamadas a a Paint 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 explícita de miembro de interfaz. Sin embargo, es perfectamente válido para que una implementación explícita de miembro de interfaz 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 PaintControl método .

ejemplo final

18.6.7 Implementación de la interfaz

Una clase que hereda una implementación de interfaz puede volver a implementar la interfaz incluyiéndolo 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 las asignaciones IControl.Paint a Control.IControl.Paint no afectan a la nueva implementación en MyControl, que se asigna IControl.Paint a MyControl.Paint.

ejemplo final

Las declaraciones de miembros públicos heredados y las declaraciones de miembros de interfaz explícita heredadas participan en el proceso de asignación de interfaz para las interfaces reintencionadas.

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 en asigna los métodos de IMethods interfaz a Derived.F, Base.IMethods.G, Derived.IMethods.Hy Base.I.Derived

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, IBase.F asignando a D.F.

ejemplo final

18.6.8 Clases e interfaces abstractas

Al igual que una clase no abstracta, una clase abstracta proporcionará implementaciones de todos los miembros de las interfaces que se enumeran en la lista de clases base de la clase. Sin embargo, se permite asignar 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 mapas F y G en métodos abstractos, que se reemplazarán en clases no abstractas que derivan 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