CodeGym /Cursos /C# SELF /Implementación explícita de interfaces en C#

Implementación explícita de interfaces en C#

C# SELF
Nivel 23 , Lección 4
Disponible

1. Introducción

Normalmente, cuando implementas una interfaz, simplemente creas métodos públicos en tu clase cuyas firmas coinciden con las declaradas en la interfaz. Esto se llama implementación implícita o implementación pública. El compilador es lo bastante listo como para entender: "¡Ajá, este método DoSomething() en la clase MyClass está pensado para implementar IDoable.DoSomething()!".

Pero, ¿qué pasa si:

  • Tu clase SmartDevice implementa la interfaz ICamera (que tiene el método TakePicture()) y la interfaz IScreen (que también tiene el método TakePicture() — para hacer una captura de pantalla)?
  • O tu clase Robot ya tiene un método público Reset() para reiniciar todos los sistemas, pero también quieres que implemente la interfaz IDevice con el método Reset(), que solo debe reiniciar parte de la configuración?

En estos casos surge ambigüedad o el deseo de separar claramente la funcionalidad. Aquí es donde entra la implementación explícita de interfaces.

La implementación explícita de interfaces te permite decirle al compilador: "Este método concreto es para implementar esta interfaz, y estará disponible solo a través de una referencia a esa interfaz". Es como Peter Parker, que lanza telarañas solo cuando actúa como Spider-Man, y usa sus habilidades de fotógrafo cuando actúa como el "Peter Parker normal".

2. Cuándo y para qué sirve la implementación explícita de interfaces

Cuando implementas una interfaz de la forma habitual, sus métodos y propiedades pasan a formar parte de la interfaz pública de tu clase. Pero hay situaciones en las que quieres que solo sean accesibles a través de la interfaz, y no directamente desde la clase. ¡Esto pasa más a menudo de lo que parece! Por ejemplo, si dos interfaces requieren el mismo método (pero con un significado diferente), o si quieres limitar el acceso a la implementación solo para quienes trabajan con el objeto estrictamente a través de la interfaz.

Aquí es donde la implementación explícita de interfaces te salva. Es como un pasadizo secreto en la arquitectura del software: desde fuera no se ve, pero la gente que sabe — ¡pasa!

Escenario 1: Conflicto de nombres

Imagina que tienes una clase que implementa dos interfaces, y ambas requieren un método con el mismo nombre, pero con lógica totalmente diferente. Por ejemplo:

interface IWriter
{
    void Print();
}

interface IPrinter
{
    void Print();
}

Quieres que IWriter.Print() escriba texto en un archivo, y que IPrinter.Print() lo envíe a la impresora. Una implementación normal del método Print() no te permite separar el comportamiento. Aquí es donde la implementación explícita te salva.

Escenario 2: Ocultar métodos de interfaz técnicos/no relevantes

A veces tu clase está obligada a implementar un método de interfaz, pero no quieres ofrecérselo a todos los consumidores de la clase (por ejemplo, el miembro de la interfaz solo se necesita para trabajo interno en la infraestructura).

Escenario 3: Protección contra llamadas "por error"

Si el método de la interfaz no está pensado para ser llamado directamente (por ejemplo, un mecanismo interno del framework), puedes implementarlo explícitamente — así un programador despistado no podrá llamarlo directamente a través del objeto de la clase.

3. Sintaxis de la implementación explícita de interfaces

La diferencia principal — en la implementación explícita nombras los métodos y propiedades con el nombre completo de la interfaz. ¡Y nada de modificadores de acceso (public/private) ni la palabra clave override!

Sintaxis general:


Tipo_de_retorno NombreInterfaz.NombreMetodo(parámetros)
{
    // implementación
}
Sintaxis de la implementación explícita de interfaces

Parece como si estuvieras indicando explícitamente el nombre de la interfaz, para que el compilador y tus compis no se líen sobre a qué contrato pertenece esa implementación.

Resolución de conflicto de nombres

Vamos a ver el caso de IWriter y IPrinter. Seguimos con nuestra app de ejemplo, donde, por ejemplo, tenemos una clase de informes:

interface IWriter
{
    void Print();
}

interface IPrinter
{
    void Print();
}

public class Report : IWriter, IPrinter
{
    // Implementación explícita de IWriter.Print
    void IWriter.Print()
    {
        Console.WriteLine("Guardando el informe en un archivo (Writer)...");
    }

    // Implementación explícita de IPrinter.Print
    void IPrinter.Print()
    {
        Console.WriteLine("Enviando el informe a la impresora física (Printer)...");
    }

    // Método adicional para mostrar al usuario
    public void Show()
    {
        Console.WriteLine("Mostrando el informe en pantalla.");
    }
}

Ahora probamos a usar las diferentes interfaces:

var report = new Report();

report.Show(); // Método público normal

// report.Print(); // ¡Error! No existe un método Print con ese nombre en la clase Report

IWriter writer = report;
writer.Print(); // Llama a la implementación de IWriter.Print()

IPrinter printer = report;
printer.Print(); // Llama a la implementación de IPrinter.Print()

Aquí los métodos Print no están disponibles a través del objeto report, pero sí a través de la interfaz adecuada. Ese es el núcleo de la implementación explícita: solo a través del "puerto" de la interfaz llegas al método que quieres.

Cómo se ve en memoria: una ilustración sencilla

En esencia, la implementación explícita "esconde" el miembro de la interfaz dentro de la clase. Para visualizarlo, imagina esta tabla:

Cómo accedemos Qué se ejecuta realmente
report.Print()
Error de compilación — no existe ese método
((IWriter)report).Print()
Implementación explícita
IWriter.Print()
((IPrinter)report).Print()
Implementación explícita
IPrinter.Print()
report.Show()
Método Show de la clase

4. Ejemplo: Interfaz — solo para infraestructura

En un proyecto real es muy común este escenario: una clase debe implementar una interfaz de servicio, pero la implementación no es necesaria para los usuarios normales de la clase.

interface IBroadcastable
{
    void Broadcast();
}

public class SecretMessage : IBroadcastable
{
    void IBroadcastable.Broadcast()
    {
        Console.WriteLine("El mensaje secreto ha sido emitido...");
    }

    public void Reveal()
    {
        Console.WriteLine("Mostrando el secreto en pantalla.");
    }
}

// En código normal:
var message = new SecretMessage();
message.Reveal();  // Método de usuario

// message.Broadcast(); // ¡Error! — no existe ese método

// Solo la infraestructura sabe qué hacer:
((IBroadcastable)message).Broadcast();

Aquí el método Broadcast() — solo para quienes trabajan por contrato de interfaz.

5. Implementación explícita de propiedades e indexadores

No solo puedes implementar explícitamente métodos, sino también propiedades e incluso indexadores.

interface IDescribable
{
    string Description { get; }
}

public class Product : IDescribable
{
    // Implementación explícita de la propiedad
    string IDescribable.Description => "La descripción solo está disponible a través de la interfaz";

    // Propiedad pública normal
    public string Name { get; set; }
}

// Ejemplo de uso:
var p = new Product { Name = "Gadget" };
// p.Description; // ¡Error! No existe esa propiedad en Product

var descr = ((IDescribable)p).Description;
Console.WriteLine(descr);

6. Detalles útiles

Cómo funciona la herencia con implementación explícita

Con la herencia todo funciona de forma intuitiva: si la clase base implementa la interfaz explícitamente, la clase derivada hereda ese "comportamiento". Pero si la derivada quiere sobreescribir la implementación del método de interfaz, no puede: la implementación explícita no puede ser virtual. Es decir, no puedes sobreescribir un miembro implementado explícitamente.

Si necesitas ese comportamiento — tendrás que usar una implementación normal o pensar en el patrón del "método plantilla".

Implementación explícita e implícita


+----------------+
|   Invoice      |
+----------------+
| Show()         |   // Se puede llamar directamente
+----------------+
| ITxtExportable.Export()   // Solo a través de ITxtExportable
| IJsonExportable.Export()  // Solo a través de IJsonExportable
+----------------+

Características/limitaciones de la implementación explícita

  • Los métodos y propiedades implementados explícitamente no pueden tener modificadores de acceso. Por defecto son privados para el mundo exterior y solo accesibles a través de la interfaz.
  • No puedes hacer estos miembros static, virtual, abstract ni override.
  • No puedes acceder a un miembro implementado explícitamente directamente desde el objeto de la clase (solo a través de una variable de tipo interfaz).
  • Si una interfaz hereda de otra, puedes implementar explícitamente miembros de cualquier nivel de la jerarquía.

7. Ventajas de la implementación explícita

La implementación explícita no es solo un truco sintáctico para resolver colisiones. Tiene varias razones de peso para existir:

Resolución de colisiones de nombres (The Big One): Es la razón principal y más obvia. Si dos interfaces que implementas declaran métodos (o propiedades) con las mismas firmas, la implementación explícita te permite dar implementaciones separadas y específicas para cada interfaz. Sin esto, tendrías ambigüedad.
Ejemplo del mundo real: Imagina que tienes una impresora. Puede ser también un escáner. La interfaz IPrinter tiene el método Print(), y la interfaz IScanner — el método Scan(). Pero, ¿y si ambas interfaces tuvieran el método ProcessDocument()? La implementación explícita te permite hacer IPrinter.ProcessDocument() para imprimir y IScanner.ProcessDocument() para escanear, y funcionarán de forma diferente.

Ocultar detalles de implementación y limpieza del API de la clase: Los métodos implementados explícitamente no forman parte del API público de tu clase. Solo están disponibles al convertir el objeto al tipo de interfaz. Esto es muy útil cuando quieres que cierta funcionalidad solo esté disponible "por contrato", y no como parte del comportamiento general de tu objeto.
Ejemplo: Creas un instrumento financiero complejo, por ejemplo, CreditCard. Puede implementar IPayable (para hacer pagos) y IAdminConfigurable (para configuraciones internas, como establecer límites). El método IAdminConfigurable.SetLimit() no debería estar disponible para cualquiera que tenga una CreditCard. Solo debe estar disponible para el sistema administrativo, que trabaja con CreditCard como IAdminConfigurable. La implementación explícita te permite mantener SetLimit() oculto del acceso general a CreditCard, haciendo el API de la clase más limpio y seguro.

Garantía de contrato: A veces un método en tu clase por casualidad tiene la misma firma que un método en la interfaz, pero no quieres que se considere su implementación. Por ejemplo, tienes una clase MyList con el método Clear() para limpiar su estado interno. Si decides que MyList debe implementar IList<T>, que también tiene Clear(), por defecto tu MyList.Clear() será la implementación de IList<T>.Clear(). Si su lógica debe ser diferente, la implementación explícita IList<T>.Clear() te permite separarlas.

Implícita vs. Explícita

Para ver la diferencia, aquí tienes una tabla comparativa:

Característica Implementación implícita Implementación explícita
Disponibilidad Disponible tanto por la clase como por la interfaz. Disponible solo por la interfaz.
Modificador de acceso Normalmente public (o protected, etc.). No tiene modificador de acceso (no puede ser public).
Sintaxis
public ReturnType MethodName(Params) { ... }
ReturnType InterfaceName.MethodName(Params) { ... }
Resolución de colisiones No resuelve, el método sirve para todas las interfaces con esa firma. Resuelve colisiones, permitiendo implementaciones diferentes.
Limpieza del API de la clase El método es parte del API público de la clase. El método no es parte del API público de la clase.
Propósito Para implementaciones generales y claras de interfaces. Para resolver colisiones o esconder lógica específica.

Como ves, son dos caras de la misma moneda, y cada una tiene su propósito. En la mayoría de los casos usarás la implementación implícita, porque es más sencilla y cómoda. La explícita es una herramienta para tareas específicas pero importantes.

8. Errores típicos al implementar interfaces explícitamente

Error nº1: el método no es visible a través del objeto de la clase.
Los métodos implementados explícitamente no están disponibles directamente desde la instancia de la clase. Suele haber confusión: el compilador dice que el método no existe, aunque sí está — solo que "escondido". Solución: convierte el objeto al tipo de interfaz, por ejemplo: (IMyInterface)obj.Method().

Error nº2: uso incorrecto de modificadores de acceso.
En la implementación explícita no puedes poner modificadores como public o override. Si lo intentas, tendrás un error de compilación — el compilador no acepta esas declaraciones.

Error nº3: intentar sobreescribir un método implementado explícitamente en una clase derivada.
Si el método de la interfaz se implementa explícitamente en la clase base, no puedes sobreescribirlo en la derivada. Esta limitación hay que tenerla en cuenta al diseñar jerarquías de clases con interfaces.

1
Cuestionario/control
Concepto de interfaz, nivel 23, lección 4
No disponible
Concepto de interfaz
Interfaces: fundamentos y contratos
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION