Заметка
Доступ к этой странице требует авторизации. Вы можете попробовать войти в систему или изменить каталог.
Доступ к этой странице требует авторизации. Вы можете попробовать сменить директорию.
Заметка
Эта статья является спецификацией компонентов. Спецификация служит проектным документом для функции. Она включает предлагаемые изменения спецификации, а также информацию, необходимую на этапе проектирования и разработки функции. Эти статьи публикуются до тех пор, пока предложенные изменения спецификации не будут завершены и включены в текущую спецификацию ECMA.
Может возникнуть некоторое несоответствие между спецификацией компонентов и завершенной реализацией. Эти различия зафиксированы в соответствующих заметках по проектированию языка (LDM).
Дополнительные сведения о процессе внедрения спецификаций функций в стандарт языка C# см. в статье о спецификациях .
Вопрос чемпиона: https://github.com/dotnet/csharplang/issues/435
Сводка
Поддержка языковых типов для подписанных и неподписанных целых чисел стандартного размера.
Мотивация заключается в сценариях взаимодействия между системами и предназначена для использования в низкоуровневых библиотеках.
Дизайн
Идентификаторы 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 ].
В MinValue или MaxValue полей нет nint или nuint, так как, кроме 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* |
УказательНа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, если они различаются.)
| Одинарный | Подпись оператора | IL |
|---|---|---|
+ |
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 |
| Двоичный | Подпись оператора | IL |
|---|---|---|
+ |
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