Sdílet prostřednictvím


Celá čísla v nativní velikosti

Poznámka

Tento článek je specifikace funkce. Specifikace slouží jako návrhový dokument pro funkci. Zahrnuje navrhované změny specifikace spolu s informacemi potřebnými při návrhu a vývoji funkce. Tyto články se publikují, dokud nebudou navrhované změny specifikace finalizovány a začleněny do aktuální specifikace ECMA.

Mezi specifikací funkce a dokončenou implementací může docházet k nějakým nesrovnalostem. Tyto rozdíly jsou zachyceny v relevantních poznámkách schůzky návrhu jazyka (LDM) .

Další informace o procesu přijetí specifikací funkcí do jazyka C# najdete v článku o specifikacích .

Problém šampiona: https://github.com/dotnet/csharplang/issues/435

Shrnutí

Jazyková podpora pro typy celých čísel s nativní velikostí, se znaménkem i bez něj.

Motivací jsou scénáře interoperability a knihovny na nízké úrovni.

Design

Identifikátory nint a nuint jsou nová kontextová klíčová slova, která představují nativní typy celých čísel se znaménkem a bez znaménka. Identifikátory jsou považovány pouze za klíčová slova, pokud vyhledávání názvů nenajde v daném umístění programu realizovatelný výsledek.

nint x = 3;
_ = nint.Equals(x, 3);

Typy nint a nuint jsou reprezentovány podkladovými typy System.IntPtr a System.UIntPtr s tím, že kompilátor poskytuje další převody a operace pro tyto typy jako nativní celočíselné typy.

Konstanty

Konstantní výrazy mohou být typu nint nebo nuint. Pro nativní int literály neexistuje žádná přímá syntaxe. Místo toho lze použít implicitní nebo explicitní přetypování jiných celočíselných konstantních hodnot: const nint i = (nint)42;.

nint konstanty jsou v rozsahu [ int.MinValue, int.MaxValue ].

nuint konstanty jsou v rozsahu [ uint.MinValue, uint.MaxValue ].

V MinValue nebo MaxValue neexistují žádná pole nint ani nuint, protože kromě nuint.MinValuenelze tyto hodnoty vygenerovat jako konstanty.

Konstantní skládání je podporováno pro všechny unární operátory { +, -, ~ } a binární operátory { +, -, *, /, %, ==, !=, <, <=, >, >=, &, |, ^, <<, >> }. Konstantní operace skládání se vyhodnocují pomocí Int32 a UInt32 operandů, nikoli nativních int pro konzistentní chování bez ohledu na platformu kompilátoru. Pokud má operace za následek konstantní hodnotu v 32bitových bitech, provádí se při kompilaci konstantní skládání. Jinak se operace provádí za běhu a nepovažuje se za konstantu.

Konverzace

Mezi nint a IntPtra mezi nuint a UIntPtrexistuje převod identity . Existuje převod identity mezi složenými typy, které se liší pouze nativními inty a podkladovými typy: pole, Nullable<>, vytvořené typy a řazené kolekce členů.

Následující tabulky popisují převody mezi speciálními typy. (IL pro každý převod zahrnuje varianty pro kontexty unchecked a checked, pokud se liší.)

Obecné poznámky k následující tabulce:

  • conv.u je převod na nativní celé číslo s nulovým rozšířením a conv.i je převod na nativní celé číslo s rozšířením znaménka.
  • checked kontexty pro rozšiřující i zužující jsou:
    • conv.ovf.* pro signed to *
    • conv.ovf.*.un pro unsigned to *
  • unchecked kontexty pro rozšiřující jsou:
    • conv.i* pro signed to * (kde * je cílová šířka)
    • conv.u* pro unsigned to * (kde * je cílová šířka)
  • Kontexty pro zúžení unchecked jsou:
    • conv.i* pro any to signed * (kde * je cílová šířka)
    • conv.u* pro any to unsigned * (kde * je cílová šířka)

Několik příkladů:

  • sbyte to nint a sbyte to nuint používají conv.i, zatímco byte to nint a byte to nuint používají conv.u, protože jsou všechny rozšiřující.
  • nint to byte a nuint to byte používají conv.u1, zatímco nint to sbyte a nuint to sbyte používají conv.i1. Pro byte, sbyte, shorta ushort je typ zásobníku int32. Takže conv.i1 je efektivně "převeden na podepsaný byte a poté rozšířen na int32", zatímco conv.u1 je efektivně "převeden na nepodepsaný byte a poté rozšířen s nulovým rozšířením na int32".
  • checked void* to nint používá conv.ovf.i.un stejným způsobem jako checked void* to long používá conv.ovf.i8.un.
Operand Cíl Přeměna IL
object nint Rozbalení unbox
void* nint PointerToVoid nop / conv.ovf.i.un
sbyte nint Implicitní číslo conv.i
byte nint Implicitní číslo conv.u
short nint Implicitní číslo conv.i
ushort nint Implicitní číslo conv.u
int nint Implicitní číslo conv.i
uint nint Explicitně číselný conv.u / conv.ovf.i.un
long nint Explicitně číselný conv.i / conv.ovf.i
ulong nint Explicitně číselný conv.i / conv.ovf.i.un
char nint Implicitní číslo conv.u
float nint Explicitně číselný conv.i / conv.ovf.i
double nint Explicitně číselný conv.i / conv.ovf.i
decimal nint Explicitně číselný long decimal.op_Explicit(decimal) conv.i / ... conv.ovf.i
IntPtr nint Identita
UIntPtr nint Žádný
object nuint Rozbalení unbox
void* nuint PointerToVoid Nop
sbyte nuint Explicitně číselný conv.i / conv.ovf.u
byte nuint Implicitní číslo conv.u
short nuint Explicitně číselný conv.i / conv.ovf.u
ushort nuint Implicitní číslo conv.u
int nuint Explicitně číselný conv.i / conv.ovf.u
uint nuint Implicitní číslo conv.u
long nuint Explicitně číselný conv.u / conv.ovf.u
ulong nuint Explicitně číselný conv.u / conv.ovf.u.un
char nuint Implicitní číslo conv.u
float nuint Explicitně číselný conv.u / conv.ovf.u
double nuint Explicitně číselný conv.u / conv.ovf.u
decimal nuint Explicitně číselný ulong decimal.op_Explicit(decimal) conv.u / ... conv.ovf.u.un
IntPtr nuint Žádný
UIntPtr nuint Identita
Výčet nint ExplicitEnumeration
Výčet nuint ExplicitEnumeration
Operand Cíl Přeměna IL
nint object Box box
nint void* PointerToVoid nop / conv.ovf.u
nint nuint Explicitně číselný conv.u (lze vynechat) / conv.ovf.u
nint sbyte Explicitně číselný conv.i1 / conv.ovf.i1
nint byte Explicitně číselný conv.u1 / conv.ovf.u1
nint short Explicitně číselný conv.i2 / conv.ovf.i2
nint ushort Explicitně číselný conv.u2 / conv.ovf.u2
nint int Explicitně číselný conv.i4 / conv.ovf.i4
nint uint Explicitně číselný conv.u4 / conv.ovf.u4
nint long Implicitní číslo conv.i8
nint ulong Explicitně číselný conv.i8 / conv.ovf.u8
nint char Explicitně číselný conv.u2 / conv.ovf.u2
nint float Implicitní číslo conv.r4
nint double Implicitní číslo conv.r8
nint decimal Implicitní číslo conv.i8 decimal decimal.op_Implicit(long)
nint IntPtr Identita
nint UIntPtr Žádný
nint Výčet ExplicitEnumeration
nuint object Box box
nuint void* PointerToVoid Nop
nuint nint Explicitně číselný conv.i(lze vynechat) / conv.ovf.i.un
nuint sbyte Explicitně číselný conv.i1 / conv.ovf.i1.un
nuint byte Explicitně číselný conv.u1 / conv.ovf.u1.un
nuint short Explicitně číselný conv.i2 / conv.ovf.i2.un
nuint ushort Explicitně číselný conv.u2 / conv.ovf.u2.un
nuint int Explicitně číselný conv.i4 / conv.ovf.i4.un
nuint uint Explicitně číselný conv.u4 / conv.ovf.u4.un
nuint long Explicitně číselný conv.u8 / conv.ovf.i8.un
nuint ulong Implicitní číslo conv.u8
nuint char Explicitně číselný conv.u2 / conv.ovf.u2.un
nuint float Implicitní číslo conv.r.un conv.r4
nuint double Implicitní číslo conv.r.un conv.r8
nuint decimal Implicitní číslo conv.u8 decimal decimal.op_Implicit(ulong)
nuint IntPtr Žádný
nuint UIntPtr Identita
nuint Výčet ExplicitEnumeration

Převod z A na Nullable<B> je:

  • implicitní nulovatelný převod, pokud existuje převod identity nebo implicitní převod z A na B;
  • explicitní nulovatelný převod, pokud existuje explicitní převod z A na B;
  • v opačném případě je neplatný.

Převod z Nullable<A> na B je:

  • explicitní převod s možnou hodnotou null, pokud existuje převod identity nebo implicitní nebo explicitní číselný převod z A na B;
  • v opačném případě je neplatný.

Převod z Nullable<A> na Nullable<B> je:

  • převod identity, pokud existuje převod identity z A na B;
  • explicitní nulový převod, pokud existuje implicitní nebo explicitní číselný převod z A na B;
  • v opačném případě je neplatný.

Operátoři

Předdefinované operátory jsou následující. Tyto operátory jsou považovány za přetížené na základě normálních pravidel implicitních převodů , pokud alespoň jeden z operandů je typu nint nebo nuint.

(Il pro každého operátora obsahuje varianty pro kontexty unchecked a checked, pokud se liší.)

Unární Podpis operátoru 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
Binární Podpis operátoru 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

U některých binárních operátorů podporují operátory IL další typy operandů (viz tabulka typů ECMA-335 III.1.5 Operand). Sada typů operandů podporovaných jazykem C# je ale omezená pro jednoduchost a konzistenci s existujícími operátory v jazyce.

Jsou podporovány zdvižené verze operátorů, kde jsou argumenty a návratové typy nint? a nuint?.

Operace složeného přiřazení x op= y, kde x nebo y jsou nativní inty, se řídí stejnými pravidly jako u jiných primitivních typů s předem definovanými operátory. Konkrétně je výraz vázán jako x = (T)(x op y), kde T je typ x a kde x je vyhodnocen pouze jednou.

Operátory posunu by měly maskovat počet bitů, které se mají posunout – na 5 bitů, pokud sizeof(nint) je 4 a 6 bitů, pokud sizeof(nint) je 8. (Viz §12.11) ve specifikaci jazyka C#).

Kompilátor C#9 bude hlásit chyby vazby na předdefinované nativní celočíselné operátory při kompilaci s dřívější jazykovou verzí, ale umožní používat předdefinované převody do a z nativních celých čísel.

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
    }
}

Ukazatelová aritmetika

V jazyce C# nejsou žádné předdefinované operátory pro sčítání nebo odčítání nativního celočíselného posunu u ukazatele. Místo toho jsou hodnoty nint a nuint povýšeny na long a ulong a aritmetické operátory se pro tyto typy používají předdefinovaným způsobem.

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)

Binární číselné povýšení

Informativní text binárních číselných propagací (viz §12.4.7.3ve specifikaci jazyka C#) se aktualizuje následujícím způsobem:

  • V opačném případě, je-li jeden operand typu ulong, druhý operand je převeden na typ ulong, nebo dojde k chybě doby vazby, pokud druhý operand je typu sbyte, short, int, nintnebo long.
  • Jinak, pokud je jeden operand typu nuint, druhý operand je převeden na typ nuint, nebo dojde k chybě vazby v případě, že druhý operand je typu sbyte, short, int, nintnebo long.
  • V opačném případě je-li jeden operand typu long, druhý operand je převeden na typ long.
  • V opačném případě, je-li jeden operand typu uint a druhý operand je typu sbyte, short, nint, nebo int, oba operandy jsou převedeny na typ long.
  • V opačném případě je-li jeden operand typu uint, druhý operand je převeden na typ uint.
  • Jinak, pokud je některý operand typu nint, druhý operand je převeden na typ nint.
  • V opačném případě jsou oba operandy převedeny na typ int.

Dynamický

Převody a operátory jsou syntetizovány kompilátorem a nejsou součástí podkladových IntPtr a UIntPtr typů. V důsledku toho nejsou tyto převody a operátory k dispozici z pořadače modulu runtime pro 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'

Členy typu

Jediný konstruktor pro nint nebo nuint je konstruktor bez parametrů.

Z nebo jsou výslovně vyloučeni následující členové a :

// 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();

Zbývající členové System.IntPtr a System.UIntPtrjsou implicitně zahrnuti do nint a nuint. Pro rozhraní .NET Framework 4.7.2:

public override bool Equals(object obj);
public override int GetHashCode();
public override string ToString();
public string ToString(string format);

Rozhraní implementovaná System.IntPtr a System.UIntPtrjsou implicitně zahrnuta v nint a nuint, přičemž výskyty základních typů jsou nahrazeny odpovídajícími nativními celočíselnými typy. Pokud například IntPtr implementuje ISerializable, IEquatable<IntPtr>, IComparable<IntPtr>, nint implementuje ISerializable, IEquatable<nint>, IComparable<nint>.

Přepsání, skrytí a implementace

nint a System.IntPtra nuint a System.UIntPtrjsou považovány za ekvivalenty pro přepsání, skrytí a implementaci.

Přetížení se nemohou lišit pouze podle nint a System.IntPtr, ani pouze podle nuint a System.UIntPtr. Přepsání a implementace se mohou lišit pouze podle nint a System.IntPtr, nebo nuint a System.UIntPtr. Metody skrývají jiné metody, které se liší pouze v parametrech nint a System.IntPtr, nebo nuint a System.UIntPtr.

Různé

Výrazy nint a nuint, použité jako indexy pole, se emitují bez převodu.

static object GetItem(object[] array, nint index)
{
    return array[index]; // ok
}

nint a nuint nelze použít jako základní typ enum z jazyka C#.

enum E : nint // error: byte, sbyte, short, ushort, int, uint, long, or ulong expected
{
}

Čtení a zápisy jsou atomické pro nint a nuint.

Pole mohou být označena volatile pro typy nint a nuint. ECMA-334 15.5.4 nezahrnuje enum se základním typem System.IntPtr ani System.UIntPtr.

default(nint) a new nint() jsou ekvivalentní (nint)0; default(nuint) a new nuint() jsou ekvivalentní (nuint)0.

typeof(nint) je typeof(IntPtr); typeof(nuint) je typeof(UIntPtr).

sizeof(nint) a sizeof(nuint) jsou podporované, ale vyžadují kompilaci v nebezpečném kontextu (podle potřeby pro sizeof(IntPtr) a sizeof(UIntPtr)). Hodnoty nejsou konstanty kompilace. sizeof(nint) se implementuje jako sizeof(IntPtr) místo IntPtr.Size; sizeof(nuint) se implementuje jako sizeof(UIntPtr) místo UIntPtr.Size.

Diagnostika kompilátoru pro odkazy na typy zahrnující nint nebo nuint hlásí nint nebo nuint místo IntPtr nebo UIntPtr.

Metaúdaje

nint a nuint jsou reprezentovány v metadatech jako System.IntPtr a System.UIntPtr.

Odkazy na typy, které obsahují nint nebo nuint, se emitují s System.Runtime.CompilerServices.NativeIntegerAttribute, aby indikovali, které části odkazu na typ jsou nativní čísla integer.

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;
    }
}

Kódování odkazů na typ s NativeIntegerAttribute je popsáno v NativeIntegerAttribute.md.

Alternativy

Alternativou k výše uvedenému přístupu k vymazání typu je zavedení nových typů: System.NativeInt a System.NativeUInt.

public readonly struct NativeInt
{
    public IntPtr Value;
}

Různé typy by umožňovaly přetížení odlišné od IntPtr a umožnily by specifické parsování a ToString(). CLR by mělo více práce, aby tyto typy zvládalo efektivně, což zmaří primární účel této funkce - efektivitu. Spolupráce s existujícím nativním int kódem, který používá IntPtr, by byla obtížnější.

Další alternativou je přidání nativní podpory int pro IntPtr v rámci, ale bez podpory konkrétního kompilátoru. Kompilátor automaticky podporuje všechny nové převody a aritmetické operace. Jazyk ale neposkytuje klíčová slova, konstanty ani checked operace.

Schůzky o designu