Почему инициализаторы коллекции С# работают таким образом?
Я искал инициализаторы коллекции С# и нашел реализацию очень прагматичной, но также очень не похожей на что-либо еще в С#
Я могу создать такой код:
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(), они не должны требовать ничего другого.