Revisando un earlier question en SO, comencé a pensar en la situación en la que una clase expone un valor, como una colección, como una interfaz implementada por el tipo de valor. En el siguiente ejemplo de código, estoy usando una lista y expongo esa lista como IEnumerable.Transmisión desde la interfaz al tipo subyacente
La exposición de la lista a través de la interfaz IEnumerable define la intención de que la lista solo se enumere, no se modifique. Sin embargo, dado que la instancia puede volver a enviarse a una lista, la lista en sí misma puede, por supuesto, modificarse.
También incluyo en la muestra una versión del método que impide la modificación copiando las referencias de los elementos de la lista a una nueva lista cada vez que se llama al método, impidiendo así los cambios en la lista subyacente.
Así que mi pregunta es, ¿debería todo el código exponer un tipo concreto como una interfaz implementada hacerlo por medio de una operación de copia? ¿Habría valor en una construcción de lenguaje que indique explícitamente "Quiero exponer este valor a través de una interfaz, y el código de llamada solo debería poder usar este valor a través de la interfaz"? ¿Qué técnicas usan otras personas para evitar efectos colaterales no deseados como estos al exponer valores concretos a través de sus interfaces?
Tenga en cuenta, entiendo que el comportamiento ilustrado es el comportamiento esperado. No pretendo que este comportamiento sea incorrecto, solo que permite el uso de funcionalidades que no sean las expresadas. Tal vez estoy asignando demasiada importancia a la interfaz, pensando en ella como una restricción de funcionalidad. ¿Pensamientos?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TypeCastTest
{
class Program
{
static void Main(string[] args)
{
// Demonstrate casting situation
Automobile castAuto = new Automobile();
List<string> doorNamesCast = (List<string>)castAuto.GetDoorNamesUsingCast();
doorNamesCast.Add("Spare Tire");
// Would prefer this prints 4 names,
// actually prints 5 because IEnumerable<string>
// was cast back to List<string>, exposing the
// Add method of the underlying List object
// Since the list was cast to IEnumerable before being
// returned, the expressed intent is that calling code
// should only be able to enumerate over the collection,
// not modify it.
foreach (string doorName in castAuto.GetDoorNamesUsingCast())
{
Console.WriteLine(doorName);
}
Console.WriteLine();
// --------------------------------------
// Demonstrate casting defense
Automobile copyAuto = new Automobile();
List<string> doorNamesCopy = (List<string>)copyAuto.GetDoorNamesUsingCopy();
doorNamesCopy.Add("Spare Tire");
// This returns only 4 names,
// because the IEnumerable<string> that is
// returned is from a copied List<string>, so
// calling the Add method of the List object does
// not modify the underlying collection
foreach (string doorName in copyAuto.GetDoorNamesUsingCopy())
{
Console.WriteLine(doorName);
}
Console.ReadLine();
}
}
public class Automobile
{
private List<string> doors = new List<string>();
public Automobile()
{
doors.Add("Driver Front");
doors.Add("Passenger Front");
doors.Add("Driver Rear");
doors.Add("Passenger Rear");
}
public IEnumerable<string> GetDoorNamesUsingCopy()
{
return new List<string>(doors).AsEnumerable<string>();
}
public IEnumerable<string> GetDoorNamesUsingCast()
{
return doors.AsEnumerable<string>();
}
}
}
Y puede usar el reflejo para romper otras reglas y acceder a miembros privados. IMO, lo has declarado como un 'IEnumerable'; si alguien quiere "hacer trampa" y convertirlo en algo que no definió como parte de su API pública, entonces ese es su negocio y su riesgo. –
@Kirk Woll: ese es un buen punto sobre el uso de la reflexión como un medio para "romper las reglas". Como parte de esta pregunta, quería que los comentarios de otras personas sobre si el hecho de convertir la interfaz al tipo concreto constituía una forma de "romper las reglas", o si la mayoría lo consideraba una opción esperada que debería tenerse en cuenta en el diseño de la API. Gracias por el comentario. – JeremyDWill