Строго типизированные значения в рамке в .NET

Чтобы упаковать тип значения, вы приводите его к System.Object - это само по себе кажется мне «неправильным» (поскольку приведение должно либо преобразовать значение в другой тип (поэтому преобразование Int32 в Object должно быть действием с потерей данных, поскольку Object не имеет собственное состояние экземпляра) или преобразование указателя интерфейса в родительский (что не влияет на состояние объекта, на который указывает, и является проблемой времени компиляции). При упаковке типа значения в CLR вы копируете значение в кучу и одновременно с потерей информации об интерфейсе, когда вы действительно хотите выполнить только первую задачу (скопировать значение в кучу или хотя бы получить ссылку на него).

Java решает эту проблему с помощью сильных типов Integer и Long. Почему в .NET этого нет?

У меня есть собственная коллекция исходного кода утилиты, которую я люблю включать в другие проекты, и они включают в себя собственные реализации строго типизированных упакованных классов (таких как BoxedInt32), которые переопределяют неявные и явные операторы преобразования, поэтому они работают так же, как приведение к объекту делает, за исключением того, что нет необходимости фактически приводить к объекту (таким образом сохраняя данные типа). Итак, я могу сделать это:

private BoxedInt32 _reference;
public Int32 GetValue{ return _reference; }

Так почему же .NET после четырех основных выпусков все еще не имеет коробочных типов со строгой типизацией?


person Dai    schedule 03.09.2012    source источник
comment
Самый большой вариант использования бокса был для коллекций, и дженерики решили большую часть этого. Не могли бы вы рассказать больше о причине, по которой вам нужны эти строго типизированные коробочные типы?   -  person akton    schedule 03.09.2012
comment
@akton Когда значения совместно используются несколькими компонентами программы, но существуют вне состояния статического класса, тогда каждому компоненту программы требуется ссылка на удерживаемый объект, что может запутать код, когда все, что вам нужно, это ссылка на это значение. ref параметры решают эту проблему только в том случае, если указанному программному компоненту не нужно хранить эту ссылку в своем собственном состоянии.   -  person Dai    schedule 03.09.2012
comment
Как вы думаете, что будет отличаться в вашем BoxedInt32 от простого объекта?   -  person Marc Gravell    schedule 03.09.2012


Ответы (3)


В этом не было бы смысла. Вы бы никогда не использовали BoxedInt32 напрямую, поскольку, если бы вы знали тип заранее, было бы разумно использовать int вместо поля. Итак, единственный интересный случай: когда мы не знаем тип заранее, т.е. когда имеем дело с object. Что ж, упаковка типа значения уже отлично справляется с этим, сообщая тип как Int32.

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

Вы можете сделать коробку своими руками:

public class Box<T> where T : struct {
    private readonly T value:
    public Box(T value) { this.value = value; }
    public T Value { get { return value; } }
}

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

person Marc Gravell    schedule 03.09.2012
comment
Для структурных типов, которые реализуют изменяющиеся интерфейсы (например, List<string>.Enumerator), существует разница между приведением к типу структуры и выполнением действий над структурой в штучной упаковке. Если бы структуры могли определять пользовательские методы упаковки и распаковки (при условии, что возвращаемый тип метода упаковки должен реализовывать все интерфейсы, которые делает структура, и должен приниматься в качестве параметра метода распаковки), такие проблемы можно было бы решить красиво. С точки зрения CLR это не кажется сложным - просто проверяйте JITter каждый раз, когда он видит инструкцию box... - person supercat; 27.09.2012
comment
... реализует ли тип соответствующий метод упаковки (используйте упаковку по умолчанию, если нет), и проверяйте, когда он видит unbox, определен ли пользовательский оператор. - person supercat; 27.09.2012
comment
Это помогло бы решить пару проблем в унифицированной объектной модели. В текущей реализации упакованные структуры, которые реализуют мутирующие интерфейсы, не могут очень хорошо подчиняться контракту Object.Equals (отдельные экземпляры должны сообщать о неравных значениях независимо от содержимого полей). Кроме того, некоторые структуры, которые инкапсулируют определенные классы, было бы удобнее упаковать как классы, которые они инкапсулируют, а не как самих себя (структура со значением по умолчанию, в отличие от экземпляра класса со значением по умолчанию, может предлагать полезные методы экземпляра, но упаковка класса в структуру вызывает дополнительный уровень бокса. - person supercat; 27.09.2012
comment
@supercat Я не согласен с тем, что это проблемы, которые нужно решать. Если вам нужна нестандартная упаковка, напишите метод ToObject() и вызовите его. Все остальное, что вы упоминаете, кажется решением проблемы. - person Marc Gravell; 27.09.2012
comment
На самом деле, возможно, было бы полезнее, если бы можно было просто указать, что структура не подходит для «нормальной» упаковки и что компилятор должен отклонять преобразования упаковки, но разрешать неявные преобразования в Object или интерфейсы. К сожалению, это полностью сломает дженерики. Кроме того, еще один шаблон, который я хотел бы упростить, — это «эфемерные структуры», которые могут быть возвращены свойствами или методами с неявными преобразованиями в обычные типы. Это может быть очень полезно с плавными интерфейсами. Например, учитывая foo = foo.WithX(5).WithY(6).WithZ(7), обычный... - person supercat; 27.09.2012
comment
... реализация должна была бы создавать новый объект на каждом этапе; если бы каждый этап мог вместо этого возвращать «эфемерную структуру», которая инкапсулирует тип объекта и включает расширяющееся преобразование в тип инкапсулированного объекта, методы With в структуре могли бы изменяться и возвращать инкапсулированный объект (поскольку он будет содержать единственную ссылку на Это). До появления вывода типов можно было назвать инкапсулированный объект чем-то вроде DoNotCreateVariablesOfThisType, чтобы отговорить кого-либо от сохранения результата WithX без преобразования в базовый тип. - person supercat; 27.09.2012
comment
Обертывание элементов класса в структуры иногда было бы удобным способом создания default(T), который может делать что-то полезное, но это добавляет дополнительный уровень упаковки и косвенности, если структура приводится к интерфейсу, что кажется немного неприглядным. Что касается Equals, я действительно не знаю, как заставить структуру, реализующую мутирующий интерфейс, выполнять достойную работу по соблюдению контракта Equals в упаковке. Я могу найти решение, но я думаю, что было бы чище иметь слепок, например, из. List<T>.Enumerator до IEnumerator<T> дают объект класса, который реализует IEnumerator<T>. - person supercat; 27.09.2012

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

(потому что приведение должно либо преобразовать значение в другой тип... либо преобразовать указатель интерфейса в родителя)

Все, что происходит, когда вы приводите одно к другому, это говорит компилятору: «Пожалуйста, обработайте этот случайный набор битов немного по-другому». Биты вообще не должны преобразовываться или модифицироваться. C# и Java в целях безопасности выполняют проверку типов, но это не главное в самой операции приведения типов.

Бессмысленно рассматривать набор битов, представляющих int32, как что-то еще (например, ссылку на объект), поэтому система Boxing работает за вашей спиной и создает для вас оболочку. Он по-прежнему следует правилам приведения — теперь компилятор может рассматривать эту оболочку бокса как ссылку на объект, и исходные биты не изменяются.

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

Опять же, это основано на неверном предположении. В соответствии с приведенным выше объяснением приведение типов «сохраняет данные типа» в силу того факта, что оно не изменяет значение, а просто изменяет способ интерпретации этих данных компилятором. Обертки, сгенерированные боксом, по-прежнему имеют доступ к базовым данным. Вы можете вызвать .GetType() для упакованного int, и он с радостью сообщит, что это Int32. Ваш класс BoxedInt32 просто тратит время и место на хранение уже доступных данных.

**Примечание. Иногда код, который выглядит как приведение в C#, на самом деле преобразует данные путем вызова оператора неявного преобразования. Это не кастинг, это просто притворство, так что правила не действуют.

person Orion Edwards    schedule 03.09.2012

Может быть, это было бы удобно для этого случая:

var myDictionary = new Dictionary<int, bool>();

// So we could use a BoxedBool here and modify the
// value set below.
//var myDictionary = new Dictionary<int, BoxedBool>();

myDictionary.Add(1, true);
myDictionary.Add(2, false);

foreach (var key in myDictionary.Keys)
{
    myDictionary[key] = false;
    // Cannot do this as it modified the collection.
    // If it was a reference type, this would not be a problem?
}
person Shiv    schedule 28.01.2014