Comenzando con C # 8.0 en .NET Core 3.0, al crear un miembro de una interfaz, puede determinar su implementación. El escenario más común es agregar miembros de forma segura a una interfaz ya lanzada y utilizada por innumerables clientes.
En esta guía aprenderá cómo:
- Es seguro extender las interfaces agregando métodos con implementaciones.
- Cree implementaciones parametrizadas para una mayor flexibilidad.
- Obtenga el derecho de implementar implementaciones más específicas con la posibilidad de control manual.

Por donde empezar
Primero debe configurar la máquina para que funcione con .NET Core, incluido el compilador de la vista previa de C # 8.0. Dicho compilador está disponible a partir de Visual Studio 2019 o con el nuevo SDK de vista previa de .NET Core 3.0 . Los miembros de interfaz predeterminados están disponibles a partir de .NET Core 3.0 (Vista previa 4).
Resumen del escenario
Este tutorial comienza con la primera versión de la biblioteca de relaciones con el cliente. Puede obtener la aplicación de inicio en nuestro repositorio en GitHub . La compañía que creó esta biblioteca asumió que los clientes con aplicaciones existentes las adaptarían a esta biblioteca. A los usuarios se les presentaron las definiciones mínimas de interfaz para la implementación:
public interface ICustomer { IEnumerable<IOrder> PreviousOrders { get; } DateTime DateJoined { get; } DateTime? LastOrder { get; } string Name { get; } IDictionary<DateTime, string> Reminders { get; } }
También se definió una segunda interfaz que muestra el orden:
public interface IOrder { DateTime Purchased { get; } decimal Cost { get; } }
Basado en estas interfaces, el equipo puede construir una biblioteca para que sus usuarios creen la mejor experiencia para los clientes. El objetivo del equipo era aumentar el nivel de interacción con los clientes existentes y desarrollar relaciones con los nuevos.
Es hora de actualizar la biblioteca para la próxima versión. Una de las características más populares es la adición de descuentos a clientes leales que hacen una gran cantidad de pedidos. Este nuevo descuento individual se aplica cada vez que un cliente realiza un pedido. Con cada implementación de ICustomer, se pueden establecer diferentes reglas para un descuento por fidelidad.
La forma más conveniente de agregar esta función es expandir la interfaz de ICustomer
con cualquier descuento. Esta propuesta ha causado preocupación entre los desarrolladores experimentados. “¡Las interfaces son inmutables después del lanzamiento! ¡Este es un cambio crítico! ” En C # 8.0, se han agregado implementaciones de interfaz predeterminadas para actualizar interfaces. Los autores de la biblioteca pueden agregar nuevos miembros e implementarlos de manera predeterminada
La implementación predeterminada de las interfaces permite a los desarrolladores actualizar la interfaz, al tiempo que permite que otros desarrolladores anulen esta implementación. Los usuarios de la biblioteca pueden aceptar la implementación predeterminada como un cambio no crítico.
Actualización utilizando miembros de interfaz predeterminados
El equipo estuvo de acuerdo con la implementación predeterminada más probable: un descuento en la lealtad del cliente.
La actualización debe ser funcional para establecer dos propiedades: el número de pedidos necesarios para recibir un descuento y el porcentaje del descuento. Esto lo convierte en un script ideal para miembros de interfaz predeterminados. Puede agregar un método a la interfaz ICustomer y proporcionar su implementación más probable. Todas las implementaciones existentes y nuevas pueden implementarse de manera predeterminada o tener su propia configuración.
Primero agregue un nuevo método a la implementación:
El autor de la biblioteca escribió la primera prueba para verificar la implementación:
SampleCustomer c = new SampleCustomer("customer one", new DateTime(2010, 5, 31)) { Reminders = { { new DateTime(2010, 08, 12), "childs's birthday" }, { new DateTime(1012, 11, 15), "anniversary" } } }; SampleOrder o = new SampleOrder(new DateTime(2012, 6, 1), 5m); c.AddOrder(o); o = new SampleOrder(new DateTime(2103, 7, 4), 25m); c.AddOrder(o);
Presta atención a la siguiente parte de la prueba:
Esta parte, desde SampleCustomer
hasta ICustomer
es importante. La clase SampleCustomer
no SampleCustomer
proporcionar una implementación para ComputeLoyaltyDiscount
; Esto lo proporciona la interfaz ICustomer
. Sin embargo, la clase SampleCustomer
no hereda miembros de sus interfaces. Esta regla no ha cambiado. Para llamar a cualquier método implementado en una interfaz, la variable debe ser un tipo de interfaz, en este ejemplo ICustomer
.
Parametrización
Este es un buen comienzo. Pero la implementación predeterminada es demasiado limitada. Muchos consumidores de este sistema pueden elegir diferentes umbrales para el número de compras, diferentes duraciones de membresía o diferentes descuentos en porcentaje. Puede mejorar el proceso de actualización para más clientes al proporcionar una forma de establecer estos parámetros. Agreguemos un método estático que establece estos tres parámetros que controlan la implementación predeterminada:
Este pequeño fragmento de código muestra muchas características nuevas del lenguaje. Las interfaces ahora pueden incluir miembros estáticos, incluidos campos y métodos. También se incluyen varios modificadores de acceso. Los campos adicionales son privados y el nuevo método es público. Cualquiera de los modificadores está permitido para los miembros de la interfaz.
Las aplicaciones que usan la fórmula general para calcular los descuentos de lealtad, pero con diferentes parámetros, no deberían proporcionar una implementación personalizada; pueden establecer argumentos por el método estático. Por ejemplo, el siguiente código establece la "apreciación del cliente", que recompensa a cualquier cliente con una membresía de más de un mes:
ICustomer.SetLoyaltyThresholds(new TimeSpan(30, 0, 0, 0), 1, 0.25m); Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");
Amplíe la implementación predeterminada
El código que agregó anteriormente proporcionó una implementación conveniente para aquellos escenarios en los que los usuarios desean algo como la implementación predeterminada o proporcionan un conjunto de reglas no relacionadas. Para la versión final, reorganicemos un poco el código para incluir escenarios en los que los usuarios quieran confiar en la implementación predeterminada.
Considere una startup que quiera atraer nuevos clientes. Ofrecen un descuento del 50% en el primer pedido de un nuevo cliente. Los clientes existentes reciben un descuento estándar. El autor de la biblioteca necesita mover la implementación predeterminada al método protected static
para que cualquier clase que implemente esta interfaz pueda reutilizar el código en su implementación. La implementación predeterminada de un miembro de la interfaz también llama a este método genérico:
public decimal ComputeLoyaltyDiscount() => DefaultLoyaltyDiscount(this); protected static decimal DefaultLoyaltyDiscount(ICustomer c) { DateTime start = DateTime.Now - length; if ((c.DateJoined < start) && (c.PreviousOrders.Count() > orderCount)) { return discountPercent; } return 0; }
En la implementación de la clase que implementa esta interfaz, puede llamar manualmente al método auxiliar estático y extender esta lógica para proporcionar un descuento al "nuevo cliente":
public decimal ComputeLoyaltyDiscount() { if (PreviousOrders.Any() == false) return 0.50m; else return ICustomer.DefaultLoyaltyDiscount(this); }
Puede ver todo el código terminado en nuestro repositorio en GitHub .
Estas nuevas características significan que las interfaces se pueden actualizar de forma segura si existe una implementación predeterminada aceptable para los nuevos miembros. Diseñe interfaces cuidadosamente para expresar ideas funcionales individuales que puedan ser implementadas por varias clases. Esto facilita la actualización de estas definiciones de interfaz cuando se encuentran nuevos requisitos para la misma idea funcional.