Megosztás a következőn keresztül:


Natív méretű egész számok

Jegyzet

Ez a cikk egy funkcióspecifikáció. A specifikáció a funkció tervezési dokumentumaként szolgál. Tartalmazza a specifikáció javasolt módosításait, valamint a funkció tervezése és fejlesztése során szükséges információkat. Ezeket a cikkeket mindaddig közzéteszik, amíg a javasolt specifikációmódosításokat nem véglegesítik, és be nem építik a jelenlegi ECMA-specifikációba.

A szolgáltatás specifikációja és a befejezett implementáció között eltérések lehetnek. Ezeket a különbségeket a vonatkozó nyelvi tervezési értekezlet (LDM) megjegyzései rögzítik.

A funkcióspektusok C# nyelvi szabványba való bevezetésének folyamatáról a specifikációkcímű cikkben olvashat bővebben.

Bajnok-kérdés: https://github.com/dotnet/csharplang/issues/435

Összefoglalás

Nyelvi támogatás natív méretű aláírt és aláíratlan egész számokhoz.

A motiváció az interop forgatókönyvek és az alacsony szintű kódtárak esetében van.

Tervezés

Az azonosítók nint és nuint új környezetfüggő kulcsszavak, amelyek natív aláírt és aláíratlan egész számtípusokat jelölnek. Az azonosítókat csak akkor kezeli a rendszer kulcsszavakként, ha a névkeresés nem talál életképes eredményt az adott programhelyen.

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

A nint és a nuint típusokat a mögöttes típusok, System.IntPtr és System.UIntPtr képviselik, a fordító pedig további átalakításokat és műveleteket biztosít ezekhez a típusokhoz, mint natív egész számok.

Állandók

Az állandó kifejezések nint vagy nuinttípusúak lehetnek. A natív int literálokhoz nincs közvetlen szintaxis. Más integrál állandó értékek implicit vagy explicit vetületei használhatók helyette: const nint i = (nint)42;.

nint állandók az [ int.MinValue, int.MaxValue ] tartományban vannak.

nuint állandók az [ uint.MinValue, uint.MaxValue ] tartományban vannak.

Nincsenek MinValue vagy MaxValue mezők nint vagy nuint, mert a nuint.MinValuekivételével ezek az értékek nem bocsáthatók ki állandóként.

Az állandó összecsukás minden unáris operátor esetében támogatott: { +, -, ~ }, és a bináris operátorok esetében: { +, -, *, /, %, ==, !=, <, <=, >, >=, &, |, ^, <<, >> }. Az állandó összecsukási műveletek kiértékelése a fordítóplatformtól függetlenül Int32 és UInt32 operandusokkal történik, nem pedig natív intekkel a konzisztens működés érdekében. Ha a művelet 32 bites állandó értéket eredményez, az állandó összecsukás fordítási időpontban történik. Ellenkező esetben a művelet futásidőben lesz végrehajtva, és nem tekinthető állandónak.

Konverziók

A nint és a IntPtr, valamint a nuint és a UIntPtrközötti identitáskonvertálás történik. Az összetett típusok közötti azonossági konverzió csak a natív egész számok és a mögöttes típusok különbségein alapul: tömbök, Nullable<>, konstruált típusok és tuple-ök.

Az alábbi táblázatok a speciális típusok közötti átalakításokat ismertetik. (Az IL minden átalakításhoz tartalmazza a unchecked és checked környezetek variánsait, ha eltérnek.)

Az alábbi táblázat általános megjegyzései:

  • conv.u a natív egész számra való nulla kiterjesztésű átalakítás, conv.i pedig a natív egész számra való előjeles kiterjesztésű átalakítás.
  • checked és szűkítő környezetei a következők:
    • conv.ovf.* a signed to *
    • conv.ovf.*.un a unsigned to *
  • unchecked szélesebb környezetei a következők:
    • conv.i* signed to *-hez (ahol * a célszélesség)
    • conv.u* unsigned to *-hez (ahol * a célszélesség)
  • unchecked szűkítési környezetei a következők:
    • conv.i* any to signed *-hez (ahol * a célszélesség)
    • conv.u* any to unsigned *-hez (ahol * a célszélesség)

Néhány példa:

  • sbyte to nint és sbyte to nuintconv.i használnak, míg byte to nint és byte to nuintconv.u használnak, mert mind .
  • nint to byte és nuint to byte használja a conv.u1-t, míg nint to sbyte és nuint to sbyte a conv.i1-öt. byte, sbyte, shortés ushort esetén a "verem típusa" a int32. Így conv.i1 gyakorlatilag "alakítva egy előjeles bájttá, majd előjelkiterjesztve int32-re", míg conv.u1 gyakorlatilag "alakítva egy előjel nélküli bájttá, majd nullakiterjesztve int32-re".
  • checked void* to nint ugyanúgy használja a conv.ovf.i.un, mint checked void* to longconv.ovf.i8.un.
Operandus Cél Konverzió IL
object nint Kicsomagolás unbox
void* nint PointerToVoid nop / conv.ovf.i.un
sbyte nint ImplicitNumerikus conv.i
byte nint ImplicitNumerikus conv.u
short nint ImplicitNumerikus conv.i
ushort nint ImplicitNumerikus conv.u
int nint ImplicitNumerikus 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 ImplicitNumerikus 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 Azonosság
UIntPtr nint Egyik sem
object nuint Kicsomagolás unbox
void* nuint PointerToVoid nop
sbyte nuint ExplicitNumeric conv.i / conv.ovf.u
byte nuint ImplicitNumerikus conv.u
short nuint ExplicitNumeric conv.i / conv.ovf.u
ushort nuint ImplicitNumerikus conv.u
int nuint ExplicitNumeric conv.i / conv.ovf.u
uint nuint ImplicitNumerikus conv.u
long nuint ExplicitNumeric conv.u / conv.ovf.u
ulong nuint ExplicitNumeric conv.u / conv.ovf.u.un
char nuint ImplicitNumerikus 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 Egyik sem
UIntPtr nuint Azonosság
Felsorolás nint Kifejezett felsorolás
Felsorolás nuint Kifejezett felsorolás
Operandus Cél Konverzió IL
nint object Ökölvívás box
nint void* PointerToVoid nop / conv.ovf.u
nint nuint ExplicitNumeric conv.u (kihagyható) / 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 ImplicitNumerikus conv.i8
nint ulong ExplicitNumeric conv.i8 / conv.ovf.u8
nint char ExplicitNumeric conv.u2 / conv.ovf.u2
nint float ImplicitNumerikus conv.r4
nint double ImplicitNumerikus conv.r8
nint decimal ImplicitNumerikus conv.i8 decimal decimal.op_Implicit(long)
nint IntPtr Azonosság
nint UIntPtr Egyik sem
nint Felsorolás Kifejezett felsorolás
nuint object Ökölvívás box
nuint void* PointerToVoid nop
nuint nint ExplicitNumeric conv.i(kihagyható) / 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 ImplicitNumerikus conv.u8
nuint char ExplicitNumeric conv.u2 / conv.ovf.u2.un
nuint float ImplicitNumerikus conv.r.un conv.r4
nuint double ImplicitNumerikus conv.r.un conv.r8
nuint decimal ImplicitNumerikus conv.u8 decimal decimal.op_Implicit(ulong)
nuint IntPtr Egyik sem
nuint UIntPtr Azonosság
nuint Felsorolás Kifejezett felsorolás

Az A-ról Nullable<B>-ra való átalakítás a következő:

  • implicit null értékű átalakítás, ha identitásátalakítás vagy implicit átalakítás történik A-ról B;
  • explicit null értékű konverzió, ha van explicit konverzió a A-ről a B.
  • egyéb esetben érvénytelen.

Az Nullable<A>-ról B-ra való átalakítás a következő:

  • explicit null értékű átalakítás, ha identitásátalakítás vagy implicit vagy explicit numerikus átalakítás történik A-ról B;
  • egyéb esetben érvénytelen.

Az Nullable<A>-ról Nullable<B>-ra való átalakítás a következő:

  • identitáskonvertálás, ha létezik egy identitáskonvertálás A-ról B-re;
  • explicit nullable konverzió, ha implicit vagy explicit numerikus átalakítás történik A-ről B;
  • egyéb esetben érvénytelen.

Üzemeltetők

Az előre definiált operátorok a következők. Ezeket az operátorokat az implicit átalakításokra vonatkozó normál szabályok alapján veszik figyelembe a túlterhelés feloldása során , ha az operandusok közül legalább az egyik nint vagy nuinttípusú.

(Az il minden operátorhoz tartalmazza a unchecked és checked környezetek variánsait, ha eltérnek.)

Unary Operátor aláírása 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áris Operátor aláírása 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

Egyes bináris operátorok esetében az IL operátorok további operandustípusokat támogatnak (lásd ECMA-335 III.1.5 Operandus típusú táblázatot). A C# által támogatott operandustípusok azonban az egyszerűség és a nyelv meglévő operátoraival való konzisztenciára korlátozódnak.

Az operátorok feloldott verziói, ahol az argumentumok és a visszatérési típusok nint? és nuint?, támogatottak.

Az összetett hozzárendelési műveletek x op= y, amelyekben x vagy y natív intsek, ugyanazokat a szabályokat követik, mint más, előre definiált operátorokkal rendelkező primitív típusok esetében. Pontosabban a kifejezés x = (T)(x op y), ahol T a x típusa, és ahol a x csak egyszer értékelődik ki.

A shift operátoroknak el kell maszkolnia az eltoláshoz szükséges bitek számát – 5 bitre, ha sizeof(nint) 4, és 6 bitre, ha sizeof(nint) 8. (lásd §12.11) a C# specifikációban).

A C#9 fordító hibajelentést tesz az előre definiált natív egész számok operátoraihoz való kötésről egy korábbi nyelvi verzió összeállításakor, de lehetővé teszi a natív egész számokra és azokból származó előre definiált konverziók használatát.

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

Mutatók aritmetikája

A C#-ban nincsenek előre definiált operátorok a mutató hozzáadásához vagy kivonásához natív egész szám eltolásokkal. Ehelyett a nint és nuint értékeket long és ulong értékekre léptetik elő, és a mutató aritmetika előre definiált operátorokat használ ezekhez a típusokhoz.

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áris numerikus előléptetések

A bináris numerikus promóciók informatív szövege (lásd a §12.4.7.3a C# specifikáció szerint) a következőképpen frissül:

  • Ellenkező esetben, ha bármelyik operandus ulongtípusú, a másik operandus ulongtípussá alakul, vagy kötési idő hiba történik, ha a másik operandus sbyte, short, int, nintvagy longtípusú.
  • Ellenkező esetben, ha bármelyik operandus nuinttípusú, a másik operandus nuinttípussá alakul, vagy kötési idő hibát tapasztal, ha a másik operandus sbyte, short, int, nintvagy longtípusú.
  • Ellenkező esetben, ha bármelyik operandus longtípusú, a másik operandus longtípussá alakul.
  • Ellenkező esetben, ha bármelyik operandus uint típusú, a másik operandus pedig sbyte, short, nint, vagy inttípusú, akkor mindkét operandus longtípussá alakul.
  • Ellenkező esetben, ha bármelyik operandus uinttípusú, a másik operandus uinttípussá alakul.
  • Ellenkező esetben, ha bármelyik operandus ninttípusú, a másik operandus ninttípussá alakul.
  • Ellenkező esetben mindkét operandus inttípussá lesz konvertálva.

Dinamikus

Az átalakításokat és operátorokat a fordító szintetizálja, és nem része az alapul szolgáló IntPtr és UIntPtr típusnak. Ennek következtében ezek a konverziók és operátorok nem érhetők el a dynamicfuttatókörnyezeti iratgyűjtőjéből.

nint x = 2;
nint y = x + x; // ok
dynamic d = x;
nint z = d + x; // RuntimeBinderException: '+' cannot be applied 'System.IntPtr' and 'System.IntPtr'

Tagok beírása

A nint vagy nuint egyetlen konstruktora a paraméter nélküli konstruktor.

A System.IntPtr és System.UIntPtralábbi tagjai kifejezetten ki vannak zárvanint-ből vagy nuint-ből:

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

A System.IntPtr és System.UIntPtrfennmaradó tagjai implicit módonnint és nuint. .NET-keretrendszer 4.7.2 esetén:

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

A System.IntPtr és System.UIntPtráltal implementált interfészek implicit módon szerepelnek nint és nuintközött, ahol az alapul szolgáló típusok helyett a megfelelő natív egész típusok találhatók. Ha például IntPtr implementálja ISerializable, IEquatable<IntPtr>, IComparable<IntPtr>, akkor nint implementálja ISerializable, IEquatable<nint>, IComparable<nint>.

Felülbírálás, elrejtés és megvalósítás

nint és System.IntPtr, valamint nuint és System.UIntPtregyenértékűnek minősülnek a felülbírálás, elrejtés és végrehajtás szempontjából.

A túlterhelések nem különbözhetnek meg csak nint és System.IntPtr, valamint nuint és System.UIntPtralapján. A felülbírálások és implementációk eltérhetnek kizárólag a nint és System.IntPtr, vagy a nuint és System.UIntPtralapján. A metódusok elrejtik azokat a metódusokat, amelyek kizárólag nint és System.IntPtr, vagy nuint és System.UIntPtralapján különböznek.

Vegyes

nint és nuint tömbindexként használt kifejezések konvertálás nélkül lesznek kibocsátva.

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

nint és nuint nem használható enum alaptípusként a C#-ból.

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

Az olvasások és írások nint és nuintesetén atomiak.

A mezők volatile jelölhetők a nint és nuinttípusok esetén. ECMA-334 15.5.4 azonban nem tartalmazza az enumSystem.IntPtr vagy System.UIntPtr alaptípusokat.

default(nint) és new nint() egyenértékűek a (nint)0; default(nuint) és new nuint() egyenértékűek a (nuint)0.

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

A sizeof(nint) és a sizeof(nuint) támogatottak, de nem biztonságos környezetben történő összeállítást igényelnek (sizeof(IntPtr) és sizeof(UIntPtr)). Az értékek nem fordítási időállandók. A sizeof(nint)sizeof(IntPtr)-ként valósul meg, nem pedig IntPtr.Size-ként; A sizeof(nuint)sizeof(UIntPtr)-ként valósul meg, nem pedig UIntPtr.Size-ként.

A típushivatkozások fordítódiagnosztikái nint vagy nuint esetében nint-t vagy nuint-at jelentenek, nem pedig IntPtr-et vagy UIntPtr-öt.

Metaadatok

nint és nuintSystem.IntPtr és System.UIntPtrmetaadatokban jelennek meg.

A nint vagy nuint tartalmazó típushivatkozásokat egy System.Runtime.CompilerServices.NativeIntegerAttribute bocsátja ki, amely jelzi, hogy a típushivatkozás mely részei natív intsek.

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

A típushivatkozások NativeIntegerAttribute kódolását a NativeIntegerAttribute.mdismerteti.

Alternatívák

A fenti "típustörlés" módszer alternatíva az új típusok bevezetése: System.NativeInt és System.NativeUInt.

public readonly struct NativeInt
{
    public IntPtr Value;
}

A különböző típusok lehetővé tennék a IntPtr-tól eltérő túlterhelést, valamint a különböző elemzések és ToString()használatát. De több munka lenne a CLR számára, hogy hatékonyan kezelje ezeket a típusokat, ami legyőzi a funkció elsődleges célját - a hatékonyságot. A IntPtr használó natív int-kódokkal való együttműködés pedig nehezebb lenne.

Egy másik lehetőség, hogy további natív integer támogatás bővítését adjuk hozzá a IntPtr keretrendszerhez, konkrét fordítói támogatás nélkül. A fordító automatikusan támogatja az új konverziókat és aritmetikai műveleteket. A nyelv azonban nem biztosít kulcsszavakat, állandókat vagy checked műveleteket.

Tervezési értekezletek