In .NET 4.5 / C# 5 wordt IReadOnlyCollection<T>
gedeclareerd met een Count
eigenschap:
public interface IReadOnlyCollection<out T> : IEnumerable<T>, IEnumerable
{
int Count { get; }
}
Ik vraag me af of het niet logisch was geweest als ICollection<T>
ook de IReadOnlyCollection<T>
-interface had geïmplementeerd:
public interface ICollection<T> : IEnumerable<T>, IEnumerable, *IReadOnlyCollection<T>*
Dit zou betekenen dat klassen die ICollection<T>
implementeren, automatisch IReadOnlyCollection<T>
hebben geïmplementeerd. Dit lijkt me redelijk.
De ICollection<T>
-abstractie kan worden gezien als een uitbreiding van de IReadOnlyCollection<T>
-abstractie. Merk op dat bijvoorbeeld List<T>
zowel ICollection<T>
als IReadOnlyCollection<T>
implementeert.
Het is echter niet zo ontworpen.
Wat mis ik hier? Waarom zou in plaats daarvan voor de huidige implementatie zijn gekozen?
UPDATE
Ik ben op zoek naar een antwoord dat gebruikmaakt van Object Oriented Design Reasoningom uit te leggen waarom:
- Een concrete klasse zoals
List<T>
die zowelIReadOnlyCollection<T>
enICollection<T>
is een beter ontwerp dan:
ICollection<T>
IReadOnlyCollection<T>
rechtstreeks implementeren
Houd er rekening mee dat dit in wezen dezelfde vraag is als:
- Waarom implementeert
IList<T>
IReadOnlyList<T>
niet? - Waarom implementeert
IDictionary<T>
IReadOnlyDictionary<T>
niet?
Antwoord 1, autoriteit 100%
Er zijn waarschijnlijk verschillende redenen. Hier zijn enkele:
-
Enorme achterwaartse compatibiliteitsproblemen
Hoe zou u de definitie van
ICollection<T>
schrijven? Dit ziet er natuurlijk uit:interface ICollection<T> : IReadOnlyCollection<T> { int Count { get; } }
Maar het heeft een probleem, omdat
IReadOnlyCollection<T>
ook een eigenschapCount
declareert (de compiler geeft hier een waarschuwing). Afgezien van de waarschuwing, laat het zoals het is (wat overeenkomt met het schrijven vannew int Count
) implementors om verschillendeimplementaties te hebben voor de tweeCount
eigenschappen door er minstens één expliciet te implementeren. Dit zou “amusant” kunnen zijn als de twee implementaties besloten om verschillende waarden te retourneren. Mensen zichzelf in de voet laten schieten is niet de stijl van C#.Ok, dus hoe zit het met:
interface ICollection<T> : IReadOnlyCollection<T> { // Count is "inherited" from IReadOnlyCollection<T> }
Nou, dit verbreekt alle bestaande code die besloten heeft om
Count
expliciet te implementeren:class UnluckyClass : ICollection<Foo> { int ICollection<Foo>.Count { ... } // compiler error! }
Daarom lijkt het mij dat er geen goede oplossing is voor dit probleem: of je breekt bestaande code, of je forceert een foutgevoelige implementatie bij iedereen. Dus de enige winnende zet is om niet te spelen .
Antwoord 2, autoriteit 40%
Jon was hier https://stackoverflow.com/a/12622784/395144, markeer zijn antwoord als antwoord:
int ICollection<Foo>.Count { ... } // compiler error!
Omdat interfaces expliciete implementaties kunnen hebben, is het extraheren van basisinterfaces niet achterwaarts compatibel (met basisklassen heb je dit probleem niet).
Daarom…
Collection<T> : IReadOnlyCollection<T>
List<T> : IReadOnlyList<T>
Dictionary<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>
… maar niet hun interfaces.
IMHO, ze hebben aanvankelijk een ontwerpfout gemaakt, die nu vrij onoplosbaar is (zonder dingen kapot te maken).
EDIT: verbergen helpt niet, oude (expliciete) implementaties worden nog steeds niet gebouwd (zonder de code aan te passen):
interface INew<out T> { T Get(); }
interface IOld<T> : INew<T>
{
void Set(T value);
new T Get();
}
class Old<T> : IOld<T>
{
T IOld<T>.Get() { return default(T); }
void IOld<T>.Set(T value) { }
}
‘Sample.Old’ implementeert geen interfacelid ‘Sample.INew.Get()’
Antwoord 3
Het zou semantischfout zijn, want uiteraard is niet elke ICollection
alleen-lezen.
Dat gezegd hebbende, hadden ze de interface IReadableCollection
kunnen noemen, terwijl een implementatieReadOnlyCollection
zou kunnen heten.
Ze zijn echter niet die kant op gegaan. Waarom? Ik zag een BCL-teamlid schrijven dat ze niet wilden dat de collecties-API ook zou worden ingewikkeld. (Hoewel dat al zo is, eerlijk gezegd.)
Antwoord 4
Toen ik je vraag voor het eerst las, dacht ik: “Hé, hij heeft gelijk! Dat zou het meest logisch zijn.” Maar het punt van een interface is om een contract te beschrijven – Een beschrijving van gedrag. Als uw klas IReadOnlyCollection
implementeert, moet deze alleen lezenzijn. Collection<T>
niet. Dus voor een Collection<T>
om een interface te implementeren die alleen-lezen impliceerde, zou dit tot behoorlijk vervelende problemen kunnen leiden. Consumenten van een IReadOnlyCollection
gaan bepaalde veronderstellingen maken over die onderliggende verzameling – zoals het herhalen ervan is veilig omdat niemand het kan wijzigen, enz. enz. Als het gestructureerd was zoals u suggereert, is dat misschien niet het geval wees waar!
Antwoord 5
De Objectgeoriënteerde ontwerpredeneringzou voortkomen uit de “Is A”-relatie tussen een klasse en alle interfaces die de klasse implementeert.
In .NET 4.5/c# 5.0 is List<T>
zowel een ICollection<T>
als IReadOnlyCollection<T>
. Je kunt een List<T>
maken als een van beide typen, afhankelijk van hoe je wilt dat je verzameling wordt gebruikt.
Terwijl ICollection<T>
implementatie van IReadOnlyCollection<T>
u de mogelijkheid geeft om List<T>
ofwel een ICollection<T>
of IReadOnlyCollection<T>
, dit zou zeggen dat ICollection<T>
“Is A”IReadOnlyCollection<T>
wat het niet is.
Bijgewerkt
Als elke klasse die geërfd heeft van ICollection<T>
IReadOnlyCollection<T>
implementeert, dan zou ik zeggen dat het logischer is om ICollection<T>
de interface implementeren. Aangezien dat niet het geval is, moet u nadenken over hoe het eruit zou zien als ICollection<T>
IReadOnlyCollection<T>
zou implementeren:
public interface IReadOnlyCollection<T> : IEnumerable<T>, IEnumerable{}
public interface ICollection<T> : IReadOnlyCollection<T> {}
Als je zou kijken naar de handtekening voor ICollection<T>
, lijkt het alsof het een IS-Aalleen-lezen verzameling is. Wacht, laten we eens kijken naar IReadOnlyCollection en ahh…het implementeert IEnumerable<T>
en IEnumerable
dus het hoeft niet per se een alleen-lezen verzameling te zijn. Functioneel zijn de implementaties die door de oorspronkelijke vraag worden voorgesteld hetzelfde, maar semantisch geloof ik dat het correcter is om een interface-implementatie zo hoog mogelijk te duwen.
Antwoord 6
Deze interface is meer een beschrijvingsinterface dan een echt functioneel ding.
In de voorgestelde benadering moet elke collectie worden opgevat als iets dat je alleen kunt lezen en altijd hetzelfde is. Je kon dus niets toevoegen of verwijderen na het maken.
We kunnen ons afvragen waarom de interface IReadOnlyCollection
wordt genoemd, en niet ‘IReadOnlyEnumerable’ omdat het IEnumerable<T>, IEnumerable
gebruikt. Het antwoord is eenvoudig, dit type interface zou geen zin hebben, omdat Enumerable al ‘alleen lezen’ is.
Dus laten we eens kijken in document IReadOnlyCollection(T)
De eerste (en laatste) senetece in de beschrijving zegt:
Vertegenwoordigt een sterk getypte, alleen-lezen verzameling elementen.
En ik denk dat dit alles verklaart als je weet waar sterk typenvoor is.