Rivedere un earlier question in SO, ho iniziato a pensare alla situazione in cui una classe espone un valore, come una raccolta, come un'interfaccia implementata dal tipo del valore. Nell'esempio di codice riportato di seguito, sto utilizzando un elenco e esponendo tale elenco come IEnumerable.Trasmissione da interfaccia a tipo sottostante
Esporre la lista tramite l'interfaccia IEnumerable definisce l'intento di elencare solo la lista, non di modificarla. Tuttavia, poiché l'istanza può essere rigirata nuovamente in una lista, la lista stessa può ovviamente essere modificata.
Includo anche nell'esempio una versione del metodo che impedisce la modifica copiando i riferimenti alle voci di elenco in un nuovo elenco ogni volta che viene chiamato il metodo, impedendo così le modifiche all'elenco sottostante.
Quindi la mia domanda è, se tutto il codice che espone un tipo concreto come interfaccia implementata lo fa tramite un'operazione di copia? Ci sarebbe un valore in un costrutto linguistico che indichi esplicitamente "Voglio esporre questo valore attraverso un'interfaccia, e chiamare il codice dovrebbe essere in grado di usare questo valore solo attraverso l'interfaccia"? Quali tecniche usano gli altri per prevenire effetti collaterali indesiderati come questi quando si espongono valori concreti attraverso le loro interfacce.
Nota, capisco che il comportamento illustrato è un comportamento previsto. Non sto affermando che questo comportamento è sbagliato, solo che consente l'uso di funzionalità diverse dall'intenzione espressa. Forse attribuisco troppa importanza all'interfaccia - pensandoci come un vincolo di funzionalità. Pensieri?
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>();
}
}
}
E puoi usare la riflessione per infrangere altre regole e accedere ai membri privati. IMO, lo hai dichiarato come 'IEnumerable'; se qualcuno vuole "imbrogliare" e trasmettere ciò a qualcosa che non hai definito come parte della tua API pubblica, allora è il loro business - e il loro rischio. –
@Kirk Woll - questo è un grande punto sull'uso del riflesso come mezzo per "infrangere le regole". Come parte di questa domanda, volevo il feedback di altre persone sul fatto che la fusione dell'interfaccia con il tipo concreto costituisse anche una forma di "infrangere le regole", o se la maggior parte la considerava un'opzione prevista che dovrebbe essere considerata nella progettazione dell'API. Grazie per il commento. – JeremyDWill