Почему коллекции BCL используют структуризаторы, а не классы?

StackOverflow https://stackoverflow.com/questions/3168311

Вопрос

Мы все знаем Союзные структуры злые В основном. Я также уверен, что потому, что IEnumerable<T>.GetEnumerator() Возвращает тип IEnumerator<T>, Структы сразу же в буферные данные в ссылочный тип, стоимостью больше, чем если бы они были просто ссылочными типами, чтобы начать.

Так почему, в общих коллекциях BCL сочетаются ли все перечисленные структуры. Конечно, причина была хорошей причиной. Единственное, что происходит для меня, заключается в том, что структуры могут быть легко скопированы, тем самым сохраняя состояние перечислителя на произвольной точке. Но добавление А. Copy() метод IEnumerator Интерфейс был бы менее неприятным, поэтому я не вижу этого как логическое оправдание самостоятельно.

Даже если я не согласен с дизайнерским решением, я хотел бы иметь возможность понять рассуждения позади этого.

Это было полезно?

Решение

Действительно, это по причинам эффективности. Команда BCL сделала много Исследования по этому вопросу, прежде чем решиться пойти с тем, что вы справедливо вызовите как подозрительную и опасную практику: использование мультипликационного типа.

Вы спрашиваете, почему это не вызывает бокса. Это связано с тем, что компилятор C # не генерирует код для коробки для INumerableable или IEnumerator в петле Foreach, если он может избежать этого!

Когда мы видим

foreach(X x in c)

Первое, что мы делаем, это проверьте, если C имеет метод под названием GetEnumerator. Если это делает, то мы проверяем, есть ли тип возвращений ITE, имеет метод MOVENEXT и ток свойства. Если это так, то контур FOREACH генерируется полностью использует прямые вызовы этих методов и свойств. Только если «шаблон» не может быть сопоставлен, мы возвращаемся к поиску интерфейсов.

Это имеет два желательных эффекта.

Во-первых, если коллекция, скажем, коллекция INTS, но была написана до того, как были изобретены в целях родовых типов, то он не предпринимает штраф для бокса в боксе значения тока на объект, а затем распределяет его для INT. Если ток - это свойство, которое возвращает INT, мы просто используем его.

Во-вторых, если enumerator - это тип значений, то он не содержит перечислитель в Ienumerator.

Как я уже сказал, команда BCL провела много исследований об этом и обнаружила, что подавляющее большинство времени, наказание на выделение и освобождение Перечислитель был достаточно велик, чтобы он стоил сделать это типом ценностей, хотя это может привести к тому, что могут вызвать некоторые сумасшедшие ошибки.

Например, рассмотрим это:

struct MyHandle : IDisposable { ... }
...
using (MyHandle h = whatever)
{
    h = somethingElse;
}

Вы вполне справедливо ожидаете попытку мутата H, чтобы потерпеть неудачу, и действительно, это делает. Компилятор обнаруживает, что вы пытаетесь изменить значение чего-то, что имеет в ожидании утилизации, и это может привести к тому, что объект, который необходимо расположен на самом деле не быть расположенным.

Теперь предположим, что у вас было:

struct MyHandle : IDisposable { ... }
...
using (MyHandle h = whatever)
{
    h.Mutate();
}

Что здесь происходит? Вы могли бы разумно ожидать, что компилятор сделает то, что он делает, если H было готовное поле: Сделайте копию и мутируйте копию Чтобы обеспечить, чтобы метод не выбрасывал вещи в значение, которое необходимо утилизировать.

Однако это конфликтует с нашей интуицией о том, что должно произойти здесь:

using (Enumerator enumtor = whatever)
{
    ...
    enumtor.MoveNext();
    ...
}

Мы ожидаем, что выполнение MOVENEXT внутри блока с использованием буду Переместите перечислитель к следующему, независимо от того, является ли это структурой или ref-типом.

К сожалению, компилятор C # сегодня имеет ошибку. Если вы находитесь в этой ситуации, мы выбираем, какую стратегию следовать непоследовательно. Поведение сегодня:

  • Если набратая вариабельная вариабельная вариация через метод, является нормальным локальным, то она обычно мутируется

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

К сожалению, спецификация обеспечивает небольшое руководство по этому вопросу. Явно что-то сломано, потому что мы делаем это непоследовательно, но что правильно Что делать, это совсем не ясно.

Другие советы

Методы структуры включены, когда тип структуры известен при компиляционном времени, а метод вызова через интерфейс медленный, поэтому ответ: из-за причины производительности.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top