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 public
modificadores , 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 (§7.5.2). Cuando una declaración de tipo parcial (§15.2.7) incluye una especificación de accesibilidad (a través de los public
modificadores , protected
, internal
y 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 yZ
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 salidaT
es un tipoSᵢ,... Aₑ
de interfaz o delegado construido a partir de un tipoS<Xᵢ, ... Xₑ>
genérico en el que al menos unoAᵢ
de los siguientes contiene:Xᵢ
es covariante o invariable yAᵢ
no es seguro para la salida.Xᵢ
es contravariante o invariable yAᵢ
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 tipoS<Aᵢ,... Aₑ>
de interfaz o delegado construido a partir de un tipoS<Xᵢ, ... Xₑ>
genérico en el que al menos unoAᵢ
de los siguientes contiene:Xᵢ
es covariante o invariable yAᵢ
no es seguro de entrada.Xᵢ
es contravariante o invariable yAᵢ
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 deAᵢ
aBᵢ
Xᵢ
es contravariante y existe una referencia implícita o conversión de identidad deBᵢ
aAᵢ
Xᵢ
es invariable y existe una conversión de identidad deAᵢ
aBᵢ
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 ointernal
en la interface_base de unapublic
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
sonIControl
,ITextBox
yIListBox
. En otras palabras, laIComboBox
interfaz anterior hereda miembrosSetText
ySetItems
así comoPaint
.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 elCombine
método después de reemplazar el parámetroT
de tipo porstring[,]
.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
,out
yref
. - 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 claseobject
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 enU
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 queE
derive deD
, 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
enIListCounter
es ambigua. Como se muestra en el ejemplo, la ambigüedad se resuelve mediante la conversiónx
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)
seleccionaIInteger.Add
aplicando reglas de resolución de sobrecarga de §12.6.4. Del mismo modo, la invocaciónn.Add(1.0)
seleccionaIDouble.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 elILeft.F
miembro. Por lo tanto, la invocaciónd.F(1)
seleccionaILeft.F
, aunqueIBase.F
parece no estar oculta en la ruta de acceso que conduce a través deIRight
.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 ocultaIBase.F
ILeft
IBase
, el miembro también está oculto en la ruta de acceso deIDerived
aIBase
IRight
.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
esIControl.Paint
y el nombre completo de SetText esITextBox.SetText
. En el ejemplo anterior, no es posible hacer referencia aPaint
comoITextBox.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 , ySystem.ICloneable.Clone
ICloneable.Clone
son nombres de miembro de interfaz calificados para elClone
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 yIControl
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
yIDictionary<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 elDispose
método de la interfaz mediante laIDisposable
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 porqueIComparable
no aparece en la lista de clases base deShape
y no es una interfaz base deICloneable
. Del mismo modo, en las declaracionesclass 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 deICloneable.Clone
compilación porqueICloneable
no aparece explícitamente en la lista de clases base deEllipse
.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
, noITextBox.Paint
como .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ónC
de interfaz . - Agregue a
L
cualquier interfaz base de las interfaces que ya estén enL
. - 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 enL
, haga que dos interfaces enL
sean idénticas, la declaración deC
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 listaL
de interfaz consta del<U>
yI<V>
. La declaración no es válida porque cualquier tipo construido conU
yV
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>
implementaI<object,C,string>.F<T>
implícitamente . En este caso,C.F<T>
no es necesario (ni permitido) especificar la restricciónT: object
, ya queobject
es una restricción implícita en todos los parámetros de tipo. El métodoC.G<T>
implementaI<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étodoC.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 implementarI<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 deT: 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.M
de 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 coincideI
con yM
, este miembro es la implementación deI.M
. - De lo contrario, si
S
contiene una declaración de un miembro público no estático que coincideM
con , este miembro es la implementación deI.M
. Si más de un miembro coincide, no se especifica qué miembro es la implementación deI.M
. Esta situación solo puede producirse siS
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
yB
son métodos, y el nombre, el tipo y las listas de parámetros deA
yB
son idénticos.A
yB
son propiedades, el nombre y el tipo deA
yB
son idénticos, yA
tienen los mismos descriptores de acceso queB
(A
se permite tener descriptores de acceso adicionales si no es una implementación explícita de miembro de interfaz).A
yB
son eventos, y el nombre y el tipo deA
yB
son idénticos.A
yB
son indexadores, el tipo y las listas de parámetros deA
yB
son idénticos, yA
tienen los mismos descriptores de acceso queB
(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 deC
se convierte en la implementación deClone
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 yIForm
IControl
se asignan alPaint
método enPage
. 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, elIControl
heredado porITextBox
y elIControl
heredado porIListBox
. De hecho, no hay ninguna noción de una identidad independiente para estas interfaces. En su lugar, las implementaciones deITextBox
yIListBox
comparten la misma implementación deIControl
yComboBox
simplemente se consideran para implementar tres interfaces,IControl
,ITextBox
yIListBox
.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
deClass1
se usa en laClass2's
implementación deInterface1
.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 enTextBox
oculta elPaint
método enControl
, pero no modifica la asignación deControl.Paint
enIControl.Paint
, y las llamadas a aPaint
través de instancias de clase e instancias de interfaz tendrán los siguientes efectosControl 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 deIControl.Paint
invalidando elPaintControl
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 asignacionesIControl.Paint
aControl.IControl.Paint
no afectan a la nueva implementación enMyControl
, que se asignaIControl.Paint
aMyControl.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 aDerived.F
,Base.IMethods.G
,Derived.IMethods.H
yBase.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 implementarIBase
,IBase.F
asignando aD.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
mapasF
yG
en métodos abstractos, que se reemplazarán en clases no abstractas que derivan deC
.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 invalidarFF
yGG
, lo que proporciona la implementación real deIMethods
.ejemplo final
ECMA C# draft specification