Почему инициализаторы коллекции C# работают таким образом?


Я смотрел на инициализаторы коллекции C# и обнаружил, что реализация очень прагматична, но также очень отличается от всего остального в C#

Я могу создать такой код:

using System;
using System.Collections;

class Program
{
    static void Main()
    {
        Test test = new Test { 1, 2, 3 };
    }
}

class Test : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        throw new NotImplementedException();
    }

    public void Add(int i) { }
}

так как я выполнил минимальные требования к компилятору (реализовано IEnumerable и public void Add) это работает, но, очевидно, не имеет никакого значения.

мне было интересно, что помешало команде C# создать более строгий набор требований? Другими словами, почему, для компиляции этого синтаксиса компилятор не требует, чтобы тип реализовывал ICollection? Это кажется больше в духе других функций C#.

3 56

3 ответа:

ваше наблюдение находится на месте - на самом деле, оно отражает одно, сделанное Мадсом Торгерсеном, Microsoft C# Language PM.

Mads сделал пост в октябре 2006 года на эту тему под названием что такое коллекция? в которой он написал:

признался, мы взорвали его в первом версия фреймворка с Система.Коллекции.Интерфейс ICollection, который это почти бесполезно. Но мы все исправили довольно хорошо, когда появились дженерики в сетях Framework 2.0 с: Система.Коллекции.Родовой.ICollection позволяет добавлять и удалять элементы, перечислите их, посчитайте и проверьте для членства.

очевидно, с тех пор, все бы реализовать ICollection каждый раз они делают коллекцию, верно? Не очень. Вот как мы использовали LINQ, чтобы узнать о том, какие коллекции на самом деле есть, и как это заставило нас изменить наш язык дизайн в C# 3.0.

оказывается, их всего 14 реализации ICollection<T> в рамках, но 189 классов, реализующих IEnumerable и государственной Add() метод.

есть Скрытая польза от этого подхода-если бы они основывали его на ICollection<T> интерфейс, там был бы точно один поддерживается Add() метод.

напротив, подход, который они приняли, означает, что инициализаторы для коллекции просто формируют наборы аргументов для Add() методы.

чтобы проиллюстрировать, давайте расширим ваш немного код:

class Test : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        throw new NotImplementedException();
    }

    public void Add(int i) { }

    public void Add(int i, string s) { }
}

теперь вы можете написать это:

class Program
{
    static void Main()
    {
        Test test 
            = new Test 
            {
                1, 
                { 2, "two" },
                3 
            };
    }
}

Я тоже думал об этом, и ответ, который меня больше всего удовлетворяет, заключается в том, что ICollection имеет много других методов, кроме Add, таких как: Clear, Contains, CopyTo и Remove. Удаление элементов или очистка не имеет ничего общего с возможностью поддержки синтаксиса инициализатора объекта, все, что вам нужно, это добавить().

Если бы фреймворк был разработан достаточно гранулярно, и был интерфейс ICollectionAdd, то у него был бы "идеальный" дизайн. Но я честно не думаю, что это будет добавили много значения, имея один метод на интерфейс. IEnumerable + Add кажется хакерским подходом, но когда вы думаете об этом, это лучшая альтернатива.

EDIT: это не единственный раз, когда C# подошел к проблеме с этим типом решения. Поскольку .NET 1.1, foreach использует duck typing для перечисления коллекции, все, что ваш класс должен реализовать, это GetEnumerator, MoveNext и Current. У Кирилла Осенкова есть post который также задает ваш вопрос.

(Я знаю, что я на 3 года опоздал на это, но я не был удовлетворен существующими ответами.)

почему для компиляции этого синтаксиса компилятор не делает требуется, чтобы тип реализовал ICollection?

я переверну ваш вопрос: какой смысл было бы, если бы у компилятора были требования, которые на самом деле не нужны?

неICollection классы тоже могут извлечь выгоду из синтаксиса инициализатора коллекции. Рассмотрим классы, которые позволяют добавление в них данных без предоставления доступа к ранее добавленным данным.

лично мне нравится использовать new Data { { ..., ... }, ... } синтаксис, чтобы добавить легкий, DSL-подобный взгляд на код моих модульных тестов.

на самом деле, я бы предпочел ослабить требование, чтобы я мог использовать красивый синтаксис, даже не беспокоясь о реализации IEnumerable. Инициализаторы коллекции-это чистый синтаксический сахар для добавления (), они не должны требовать ничего другого.