Поделиться через


Целые числа нативного размера

Заметка

Эта статья является спецификацией компонентов. Спецификация служит проектным документом для функции. Она включает предлагаемые изменения спецификации, а также информацию, необходимую на этапе проектирования и разработки функции. Эти статьи публикуются до тех пор, пока предложенные изменения спецификации не будут завершены и включены в текущую спецификацию 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.

Совещания по дизайну