Целые числа нативного размера
Заметка
Эта статья является спецификацией компонентов. Спецификация служит проектным документом для функции. Она включает предлагаемые изменения спецификации, а также информацию, необходимую на этапе проектирования и разработки функции. Эти статьи публикуются до тех пор, пока предложенные изменения спецификации не будут завершены и включены в текущую спецификацию ECMA.
Может возникнуть некоторое несоответствие между спецификацией компонентов и завершенной реализацией. Эти различия зафиксированы в соответствующих заметках по проектированию языка (LDM).
Дополнительные сведения о процессе внедрения спецификаций функций в стандарт языка C# см. в статье о спецификациях .
Сводка
Поддержка языковых типов для подписанных и неподписанных целых чисел стандартного размера.
Мотивация заключается в сценариях взаимодействия между системами и предназначена для использования в низкоуровневых библиотеках.
Дизайн
Идентификаторы nint
и nuint
являются новыми контекстными ключевыми словами, представляющими собственные типы целочисленных чисел со знаком и без знака.
Идентификаторы обрабатываются только как ключевые слова, если поиск имен не находит подходящий результат в данном контексте программы.
nint x = 3;
_ = nint.Equals(x, 3);
Типы nint
и nuint
представлены базовыми типами System.IntPtr
и System.UIntPtr
. Компилятор предоставляет дополнительные преобразования и операции для этих типов как для встроенных целых чисел.
Константы
Константные выражения могут быть типами nint
или nuint
.
Нет прямого синтаксиса для нативных литералов int. Вместо этого можно использовать неявные или явные приведения других целочисленных констант: const nint i = (nint)42;
.
Константы nint
находятся в диапазоне [ int.MinValue
, int.MaxValue
].
Константы nuint
находятся в диапазоне [ uint.MinValue
, uint.MaxValue
].
В nint
или nuint
полей нет MinValue
или MaxValue
, так как, кроме nuint.MinValue
, эти значения нельзя выдавать в виде констант.
Константное свертывание поддерживается для всех унарных операторов { +
, -
, ~
} и двоичных операторов { +
, -
, *
, /
, %
, ==
, !=
, <
, <=
, >
, >=
, &
, |
, ^
, <<
, >>
}.
Операции свертывания констант оцениваются с помощью операндов Int32
и UInt32
, а не встроенных целых чисел, для обеспечения согласованного поведения независимо от платформы компилятора.
Если операция приводит к постоянному значению в 32-битном формате, константное свертывание выполняется во время компиляции.
В противном случае операция выполняется во время выполнения и не считается константой.
Преобразования
Существует идентификационное преобразование между nint
и IntPtr
, а также между nuint
и UIntPtr
.
Существует преобразование идентичности между составными типами, которые различаются только встроенными целыми числами и базовыми типами: массивы, Nullable<>
, составные типы и кортежи.
В приведенных ниже таблицах рассматриваются преобразования между специальными типами.
(IL для каждого преобразования включает варианты для unchecked
и checked
контекстов, если они отличаются.)
Общие заметки в таблице ниже:
-
conv.u
является преобразованием с нулевым расширением в нативное целое число, аconv.i
— преобразованием с знаковым расширением в нативное целое число. -
checked
контексты для расширения и сужения:-
conv.ovf.*
дляsigned to *
-
conv.ovf.*.un
дляunsigned to *
-
- Контексты
unchecked
для расширения :-
conv.i*
дляsigned to *
(где * — целевая ширина) -
conv.u*
дляunsigned to *
(где * — целевая ширина)
-
-
unchecked
условия для сужения :-
conv.i*
дляany to signed *
(где * — целевая ширина) -
conv.u*
дляany to unsigned *
(где * — целевая ширина)
-
Примеры:
-
sbyte to nint
иsbyte to nuint
используютconv.i
, в то время какbyte to nint
иbyte to nuint
используютconv.u
, так как они все расширяют. -
nint to byte
иnuint to byte
используютconv.u1
, аnint to sbyte
иnuint to sbyte
используютconv.i1
. Дляbyte
,sbyte
,short
иushort
тип стека являетсяint32
. Таким образом,conv.i1
фактически преобразуется в знаковый байт и затем расширяется до int32, в то время какconv.u1
фактически преобразуется в беззнаковый байт и затем расширяется с нулями до int32. -
checked void* to nint
используетconv.ovf.i.un
так же, какchecked void* to long
используетconv.ovf.i8.un
.
Операнд | Цель | Превращение | IL |
---|---|---|---|
object |
nint |
Распаковки | unbox |
void* |
nint |
УказательНаVoid | nop / conv.ovf.i.un |
sbyte |
nint |
НеявныйЧисловой | conv.i |
byte |
nint |
НеявныйЧисловой | conv.u |
short |
nint |
НеявноеЧисловое | conv.i |
ushort |
nint |
Неявный числовой | conv.u |
int |
nint |
НеявныйЧисловой | conv.i |
uint |
nint |
ExplicitNumeric | conv.u / conv.ovf.i.un |
long |
nint |
ExplicitNumeric | conv.i / conv.ovf.i |
ulong |
nint |
ExplicitNumeric | conv.i / conv.ovf.i.un |
char |
nint |
НеявныйЧисловой | conv.u |
float |
nint |
ExplicitNumeric | conv.i / conv.ovf.i |
double |
nint |
ExplicitNumeric | conv.i / conv.ovf.i |
decimal |
nint |
ExplicitNumeric | long decimal.op_Explicit(decimal) conv.i / ... conv.ovf.i |
IntPtr |
nint |
Идентичность | |
UIntPtr |
nint |
Никакой | |
object |
nuint |
Распаковки | unbox |
void* |
nuint |
УказательНаVoid | nop |
sbyte |
nuint |
ExplicitNumeric | conv.i / conv.ovf.u |
byte |
nuint |
Неявное Числовое | conv.u |
short |
nuint |
ExplicitNumeric | conv.i / conv.ovf.u |
ushort |
nuint |
НеявныйЧисловой | conv.u |
int |
nuint |
ExplicitNumeric | conv.i / conv.ovf.u |
uint |
nuint |
НеявныйЧисловой | conv.u |
long |
nuint |
ExplicitNumeric | conv.u / conv.ovf.u |
ulong |
nuint |
ExplicitNumeric | conv.u / conv.ovf.u.un |
char |
nuint |
НеявныйЧисловой | conv.u |
float |
nuint |
ExplicitNumeric | conv.u / conv.ovf.u |
double |
nuint |
ExplicitNumeric | conv.u / conv.ovf.u |
decimal |
nuint |
ExplicitNumeric | ulong decimal.op_Explicit(decimal) conv.u / ... conv.ovf.u.un |
IntPtr |
nuint |
Никакой | |
UIntPtr |
nuint |
Идентичность | |
Перечисление | nint |
Явное перечисление | |
Перечисление | nuint |
Явное перечисление |
Операнд | Цель | Превращение | IL |
---|---|---|---|
nint |
object |
Бокс | box |
nint |
void* |
ПоинтерТуВойд | nop / conv.ovf.u |
nint |
nuint |
ExplicitNumeric |
conv.u (может быть опущено) / conv.ovf.u |
nint |
sbyte |
ExplicitNumeric | conv.i1 / conv.ovf.i1 |
nint |
byte |
ExplicitNumeric | conv.u1 / conv.ovf.u1 |
nint |
short |
ExplicitNumeric | conv.i2 / conv.ovf.i2 |
nint |
ushort |
ExplicitNumeric | conv.u2 / conv.ovf.u2 |
nint |
int |
ExplicitNumeric | conv.i4 / conv.ovf.i4 |
nint |
uint |
ExplicitNumeric | conv.u4 / conv.ovf.u4 |
nint |
long |
НеявныйЧисловой | conv.i8 |
nint |
ulong |
ExplicitNumeric | conv.i8 / conv.ovf.u8 |
nint |
char |
ExplicitNumeric | conv.u2 / conv.ovf.u2 |
nint |
float |
НеявныйЧисловой | conv.r4 |
nint |
double |
Неявный числовой | conv.r8 |
nint |
decimal |
Неявный числовой | conv.i8 decimal decimal.op_Implicit(long) |
nint |
IntPtr |
Идентичность | |
nint |
UIntPtr |
Никакой | |
nint |
Перечисление | Явное перечисление | |
nuint |
object |
Бокс | box |
nuint |
void* |
УказательНаVoid | nop |
nuint |
nint |
ExplicitNumeric |
conv.i (может быть опущено) / conv.ovf.i.un |
nuint |
sbyte |
ExplicitNumeric | conv.i1 / conv.ovf.i1.un |
nuint |
byte |
ExplicitNumeric | conv.u1 / conv.ovf.u1.un |
nuint |
short |
ExplicitNumeric | conv.i2 / conv.ovf.i2.un |
nuint |
ushort |
ExplicitNumeric | conv.u2 / conv.ovf.u2.un |
nuint |
int |
ExplicitNumeric | conv.i4 / conv.ovf.i4.un |
nuint |
uint |
ExplicitNumeric | conv.u4 / conv.ovf.u4.un |
nuint |
long |
ExplicitNumeric | conv.u8 / conv.ovf.i8.un |
nuint |
ulong |
НеявныйЧисловой | conv.u8 |
nuint |
char |
ExplicitNumeric | conv.u2 / conv.ovf.u2.un |
nuint |
float |
НеявныйЧисловой | conv.r.un conv.r4 |
nuint |
double |
НеявныйЧисловой | conv.r.un conv.r8 |
nuint |
decimal |
Неявный числовой | conv.u8 decimal decimal.op_Implicit(ulong) |
nuint |
IntPtr |
Нет | |
nuint |
UIntPtr |
Идентичность | |
nuint |
Перечисление | Явное перечисление |
Преобразование из A
в Nullable<B>
:
- неявное преобразование с возможностью NULL, если имеется тождественное преобразование или неявное преобразование из
A
вB
; - явное преобразование, допускающее значение NULL, если существует явное преобразование из
A
вB
; - в противном случае недопустимо.
Преобразование из Nullable<A>
в B
:
- явное преобразование в значение NULL, если имеется идентичное преобразование или неявное или явное числовое преобразование из
A
вB
; - в противном случае недействительно.
Преобразование из Nullable<A>
в Nullable<B>
:
- тождественное преобразование, если существует тождественное преобразование из
A
вB
; - явное преобразование, допускающее значение NULL, если имеется неявное или явное числовое преобразование из
A
вB
; - в противном случае будет недействительным.
Операторы
Предопределенные операторы приведены следующим образом.
Эти операторы учитываются при разрешении перегрузки по обычным правилам для неявных преобразований , если хотя бы один из операндов имеет тип nint
или nuint
.
(IL для каждого оператора включает варианты для контекстов unchecked
и checked
, если они различаются.)
Одинарный | Подпись оператора | Иллинойс |
---|---|---|
+ |
nint operator +(nint value) |
nop |
+ |
nuint operator +(nuint value) |
nop |
- |
nint operator -(nint value) |
neg |
~ |
nint operator ~(nint value) |
not |
~ |
nuint operator ~(nuint value) |
not |
Двоичный | Подпись оператора | Иллинойс |
---|---|---|
+ |
nint operator +(nint left, nint right) |
add / add.ovf |
+ |
nuint operator +(nuint left, nuint right) |
add / add.ovf.un |
- |
nint operator -(nint left, nint right) |
sub / sub.ovf |
- |
nuint operator -(nuint left, nuint right) |
sub / sub.ovf.un |
* |
nint operator *(nint left, nint right) |
mul / mul.ovf |
* |
nuint operator *(nuint left, nuint right) |
mul / mul.ovf.un |
/ |
nint operator /(nint left, nint right) |
div |
/ |
nuint operator /(nuint left, nuint right) |
div.un |
% |
nint operator %(nint left, nint right) |
rem |
% |
nuint operator %(nuint left, nuint right) |
rem.un |
== |
bool operator ==(nint left, nint right) |
beq / ceq |
== |
bool operator ==(nuint left, nuint right) |
beq / ceq |
!= |
bool operator !=(nint left, nint right) |
bne |
!= |
bool operator !=(nuint left, nuint right) |
bne |
< |
bool operator <(nint left, nint right) |
blt / clt |
< |
bool operator <(nuint left, nuint right) |
blt.un / clt.un |
<= |
bool operator <=(nint left, nint right) |
ble |
<= |
bool operator <=(nuint left, nuint right) |
ble.un |
> |
bool operator >(nint left, nint right) |
bgt / cgt |
> |
bool operator >(nuint left, nuint right) |
bgt.un / cgt.un |
>= |
bool operator >=(nint left, nint right) |
bge |
>= |
bool operator >=(nuint left, nuint right) |
bge.un |
& |
nint operator &(nint left, nint right) |
and |
& |
nuint operator &(nuint left, nuint right) |
and |
| |
nint operator |(nint left, nint right) |
or |
| |
nuint operator |(nuint left, nuint right) |
or |
^ |
nint operator ^(nint left, nint right) |
xor |
^ |
nuint operator ^(nuint left, nuint right) |
xor |
<< |
nint operator <<(nint left, int right) |
shl |
<< |
nuint operator <<(nuint left, int right) |
shl |
>> |
nint operator >>(nint left, int right) |
shr |
>> |
nuint operator >>(nuint left, int right) |
shr.un |
Для некоторых двоичных операторов операторы IL поддерживают дополнительные типы операндов (см. таблицу типов операндов в ECMA-335 III.1.5). Но набор типов операндов, поддерживаемых C#, ограничен для простоты и согласованности с существующими операторами на языке.
Поддерживаются версии операторов, где аргументы и типы возвращаемых значений имеют типы nint?
и nuint?
.
Операции комплексного присваивания x op= y
, где x
или y
являются целыми числами, следуют тем же правилам, что и другие примитивные типы с предопределенными операторами.
В частности, выражение привязано как x = (T)(x op y)
, где T
является типом x
и где x
вычисляется только один раз.
Операторы смены должны маскировать количество битов, которые необходимо переместить - на 5 битов, если sizeof(nint)
равен 4, и до 6 бит, если sizeof(nint)
равен 8.
(см. §12.11) в спецификации C#.
Компилятор C#9 сообщает об ошибках привязки к предопределенным собственным целым операторам при компиляции с более ранней версией языка, но позволит использовать предопределенные преобразования в собственные целые числа и из нее.
csc -langversion:9 -t:library A.cs
public class A
{
public static nint F;
}
csc -langversion:8 -r:A.dll B.cs
class B : A
{
static void Main()
{
F = F + 1; // error: nint operator+ not available with -langversion:8
F = (System.IntPtr)F + 1; // ok
}
}
Арифметика указателя
В C# нет предопределенных операторов сложения или вычитания указателей с нативными целыми числами смещения.
Вместо этого значения nint
и nuint
продвигаются до long
и ulong
, а арифметика указателей использует предопределенные операторы для этих типов.
static T* AddLeftS(nint x, T* y) => x + y; // T* operator +(long left, T* right)
static T* AddLeftU(nuint x, T* y) => x + y; // T* operator +(ulong left, T* right)
static T* AddRightS(T* x, nint y) => x + y; // T* operator +(T* left, long right)
static T* AddRightU(T* x, nuint y) => x + y; // T* operator +(T* left, ulong right)
static T* SubRightS(T* x, nint y) => x - y; // T* operator -(T* left, long right)
static T* SubRightU(T* x, nuint y) => x - y; // T* operator -(T* left, ulong right)
Двоичные числовые акции
двоичные числовые продвижения информативный текст (см. §12.4.7.3) в спецификации C#, обновляется следующим образом:
- …
- В противном случае, если либо операнд имеет тип
ulong
, другой операнд преобразуется в типulong
, или возникает ошибка времени привязки, если другой операнд имеет типsbyte
,short
,int
,nint
илиlong
.- В противном случае, если хотя бы один операнд имеет тип
nuint
, другой операнд преобразуется в типnuint
, или возникает ошибка времени привязки, если другой операнд имеет типsbyte
,short
,int
,nint
илиlong
.- В противном случае, если какой-либо операнд имеет тип
long
, другой операнд преобразуется в типlong
.- В противном случае, если либо операнд имеет тип
uint
, а другой операнда имеет типsbyte
,short
,nint
, илиint
, оба операнда преобразуются в типlong
.- В противном случае, если какой-либо операнд имеет тип
uint
, другой операнд преобразуется в типuint
.- В противном случае, если один операнд имеет тип
nint
, другой операнд преобразуется в типnint
.- В противном случае оба операнда преобразуются в тип
int
.
Динамический
Преобразования и операторы синтезируются компилятором и не являются частью базовых IntPtr
и UIntPtr
типов.
В результате эти преобразования и операторы недоступны из привязки среды выполнения для dynamic
.
nint x = 2;
nint y = x + x; // ok
dynamic d = x;
nint z = d + x; // RuntimeBinderException: '+' cannot be applied 'System.IntPtr' and 'System.IntPtr'
Члены типа
Единственным конструктором для nint
или nuint
является конструктор без параметров.
Следующие члены System.IntPtr
и System.UIntPtr
явным образом исключаются из nint
или nuint
:
// constructors
// arithmetic operators
// implicit and explicit conversions
public static readonly IntPtr Zero; // use 0 instead
public static int Size { get; } // use sizeof() instead
public static IntPtr Add(IntPtr pointer, int offset);
public static IntPtr Subtract(IntPtr pointer, int offset);
public int ToInt32();
public long ToInt64();
public void* ToPointer();
Оставшиеся члены System.IntPtr
и System.UIntPtr
, а также, неявно включены в в nint
и nuint
. Для .NET Framework 4.7.2:
public override bool Equals(object obj);
public override int GetHashCode();
public override string ToString();
public string ToString(string format);
Интерфейсы, реализованные System.IntPtr
и System.UIntPtr
, неявно включаются в nint
и nuint
, при этом базовые типы заменяются на соответствующие нативные целочисленные типы.
Например, если IntPtr
реализует ISerializable, IEquatable<IntPtr>, IComparable<IntPtr>
, nint
реализует ISerializable, IEquatable<nint>, IComparable<nint>
.
Переопределение, скрытие и реализация
nint
и System.IntPtr
, а также nuint
и System.UIntPtr
, считаются эквивалентными для переопределения, скрытия и реализации.
Перегрузки не могут отличаться только параметрами nint
и System.IntPtr
, а также nuint
и System.UIntPtr
.
Переопределения и реализации могут отличаться nint
и System.IntPtr
, а также nuint
и System.UIntPtr
.
Методы скрывают другие методы, отличающиеся от nint
и System.IntPtr
, или nuint
и System.UIntPtr
, только.
Разное
nint
и nuint
выражения, используемые в качестве индексов массива, эмитируются без преобразования.
static object GetItem(object[] array, nint index)
{
return array[index]; // ok
}
nint
и nuint
нельзя использовать в качестве базового типа enum
из C#.
enum E : nint // error: byte, sbyte, short, ushort, int, uint, long, or ulong expected
{
}
Операции чтения и записи атомарны для nint
и nuint
.
Поля могут быть помечены volatile
для типов nint
и nuint
.
Однако, ECMA-334 15.5.4 не включает enum
с базовым типом System.IntPtr
или System.UIntPtr
.
default(nint)
и new nint()
эквивалентны (nint)0
; default(nuint)
и new nuint()
эквивалентны (nuint)0
.
typeof(nint)
typeof(IntPtr)
; typeof(nuint)
typeof(UIntPtr)
.
sizeof(nint)
и sizeof(nuint)
поддерживаются, но требуют компиляции в небезопасном контексте (как это необходимо для sizeof(IntPtr)
и sizeof(UIntPtr)
).
Значения не являются константами во время компиляции.
sizeof(nint)
реализуется как sizeof(IntPtr)
, а не IntPtr.Size
; sizeof(nuint)
реализуется как sizeof(UIntPtr)
, а не UIntPtr.Size
.
Диагностика компилятора для ссылок на тип, затрагивающих nint
или nuint
, сообщает о nint
или nuint
, а не о IntPtr
или UIntPtr
.
Метаданные
nint
и nuint
представлены в метаданных как System.IntPtr
и System.UIntPtr
.
Ссылки на тип, включающие nint
или nuint
, создаются с помощью System.Runtime.CompilerServices.NativeIntegerAttribute
, чтобы указать, какие части ссылки на тип являются собственными.
namespace System.Runtime.CompilerServices
{
[AttributeUsage(
AttributeTargets.Class |
AttributeTargets.Event |
AttributeTargets.Field |
AttributeTargets.GenericParameter |
AttributeTargets.Parameter |
AttributeTargets.Property |
AttributeTargets.ReturnValue,
AllowMultiple = false,
Inherited = false)]
public sealed class NativeIntegerAttribute : Attribute
{
public NativeIntegerAttribute()
{
TransformFlags = new[] { true };
}
public NativeIntegerAttribute(bool[] flags)
{
TransformFlags = flags;
}
public readonly bool[] TransformFlags;
}
}
Кодировка ссылок на тип с помощью NativeIntegerAttribute
рассматривается в NativeIntegerAttribute.md.
Альтернативы
Альтернативой упомянутому выше подходу "type erasure" является введение новых типов: System.NativeInt
и System.NativeUInt
.
public readonly struct NativeInt
{
public IntPtr Value;
}
Различные типы позволяют перегружать отдельно от IntPtr
и обеспечивать уникальный синтаксический анализ с ToString()
.
Но для CLR было бы больше работы по эффективной обработке этих типов, что подрывает основную цель данной функции — повысить эффективность.
И взаимодействие с существующим нативным кодом int, использующим IntPtr
, было бы сложнее.
Еще одна альтернатива — добавить более встроенную поддержку int для IntPtr
в платформе, но без какой-либо определенной поддержки компилятора.
Любые новые преобразования и арифметические операции будут поддерживаться компилятором автоматически.
Но язык не будет предоставлять ключевые слова, константы или операции checked
.
Совещания по дизайну
- https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-05-26.md
- https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-06-13.md
- https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-07-05.md#native-int-and-intptr-operators
- https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-10-23.md
- https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-03-25.md
C# feature specifications