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

Я искал инициализаторы коллекции С# и нашел реализацию очень прагматичной, но также очень не похожей на что-либо еще в С#

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

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 и a public void Add), это работает, но, очевидно, не имеет значения.

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

Ответ 1

Ваше наблюдение - на самом деле, оно отражает одно, сделанное Мадс Тёргерсен, Microsoft С# Language PM.

Мэдс сделал пост в октябре 2006 года по этому вопросу под названием What Is Collection?, в котором он писал:

Допустим, мы взорвали его в первом версия рамочного System.Collections.ICollection, который рядом с бесполезным. Но мы исправили это довольно хорошо, когда появились дженерики в .NET framework 2.0: System.Collections.Generic.ICollection < Т > позволяет добавлять и удалять элементы, перечислить их, подсчитать и проверить для членства.

Очевидно, с этого момента все будут реализовать ICollection <T> каждый раз они делают коллекцию, не так ли? Не так. Вот как мы использовали LINQ для изучения о том, какие коллекции действительно есть, и как это заставило нас изменить наш язык дизайн в С# 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 
            };
    }
}

Ответ 2

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

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

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

Ответ 3

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

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

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

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

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

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