Почему коллекции BCL используют структуризаторы, а не классы?
-
02-10-2019 - |
Вопрос
Мы все знаем Союзные структуры злые В основном. Я также уверен, что потому, что 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 # сегодня имеет ошибку. Если вы находитесь в этой ситуации, мы выбираем, какую стратегию следовать непоследовательно. Поведение сегодня:
Если набратая вариабельная вариабельная вариация через метод, является нормальным локальным, то она обычно мутируется
Но если это лаплый локальный (потому что это закрытое переменная анонимной функции или в блоке итератора), то локальный является фактически генерируется в качестве поля только для чтения, а передача, которая гарантирует, что мутации случаются на копии.
К сожалению, спецификация обеспечивает небольшое руководство по этому вопросу. Явно что-то сломано, потому что мы делаем это непоследовательно, но что правильно Что делать, это совсем не ясно.
Другие советы
Методы структуры включены, когда тип структуры известен при компиляционном времени, а метод вызова через интерфейс медленный, поэтому ответ: из-за причины производительности.