Waarom implementeert generieke ICollection IReadOnlyCollection niet in .NET 4.5?

In .NET 4.5 / C# 5 wordt IReadOnlyCollection<T>gedeclareerd met een Counteigenschap:

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 zowel IReadOnlyCollection<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:

  1. Waarom implementeert IList<T>IReadOnlyList<T>niet?
  2. 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 eigenschap Countdeclareert (de compiler geeft hier een waarschuwing). Afgezien van de waarschuwing, laat het zoals het is (wat overeenkomt met het schrijven van new int Count) implementors om verschillendeimplementaties te hebben voor de twee Counteigenschappen 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 Countexpliciet 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 ICollectionalleen-lezen.

Dat gezegd hebbende, hadden ze de interface IReadableCollectionkunnen noemen, terwijl een implementatieReadOnlyCollectionzou 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 IReadOnlyCollectionimplementeert, 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 IReadOnlyCollectiongaan 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 IEnumerabledus 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 IReadOnlyCollectionwordt genoemd, en niet ‘IReadOnlyEnumerable’ omdat het IEnumerable<T>, IEnumerablegebruikt. 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.

Other episodes