Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Nota
Artikel ini adalah spesifikasi fitur. Spesifikasi berfungsi sebagai dokumen desain untuk fitur tersebut. Ini termasuk perubahan spesifikasi yang diusulkan, bersama dengan informasi yang diperlukan selama desain dan pengembangan fitur. Artikel ini diterbitkan sampai perubahan spesifikasi yang diusulkan diselesaikan dan dimasukkan dalam spesifikasi ECMA saat ini.
Mungkin ada beberapa perbedaan antara spesifikasi fitur dan implementasi yang selesai. Perbedaan tersebut dicatat dalam catatan terkait rapat desain bahasa (LDM) .
Anda dapat mempelajari lebih lanjut tentang proses untuk mengadopsi speklet fitur ke dalam standar bahasa C# dalam artikel tentang spesifikasi .
Masalah juara: https://github.com/dotnet/csharplang/issues/435
Ringkasan
Dukungan bahasa untuk jenis bilangan bulat berukuran asli yang ditandatangani dan tidak ditandatangani.
Motivasinya adalah untuk skenario interoperabilitas dan untuk perpustakaan tingkat rendah.
Desain
Pengidentifikasi nint
dan nuint
adalah kata kunci kontekstual baru yang mewakili jenis bilangan bulat asli yang ditandatangani dan tidak ditandatangani.
Pengidentifikasi hanya diperlakukan sebagai kata kunci ketika pencarian nama tidak menemukan hasil yang layak di lokasi program tersebut.
nint x = 3;
_ = nint.Equals(x, 3);
Jenis nint
dan nuint
diwakili oleh jenis dasar System.IntPtr
dan System.UIntPtr
, dimana compiler menampilkan konversi dan operasi tambahan untuk jenis tersebut sebagai bilangan bulat asli.
Konstanta
Ekspresi konstanta mungkin berjenis nint
atau nuint
.
Tidak ada sintaks langsung untuk literal integer bawaan. Cast implisit atau eksplisit dari nilai konstanta integral lainnya dapat digunakan sebagai alternatif: const nint i = (nint)42;
.
nint
konstanta berada dalam rentang [ int.MinValue
, int.MaxValue
].
nuint
konstanta berada dalam rentang [ uint.MinValue
, uint.MaxValue
].
Tidak ada bidang MinValue
atau MaxValue
pada nint
atau nuint
karena, selain nuint.MinValue
, nilai-nilai tersebut tidak dapat dipancarkan sebagai konstanta.
Lipatan konstan didukung untuk semua operator unary { +
, -
, ~
} dan operator biner { +
, -
, *
, /
, %
, ==
, !=
, <
, <=
, >
, >=
, &
, |
, ^
, <<
, >>
}.
Operasi pelipatan konstan dievaluasi dengan operand Int32
dan UInt32
alih-alih integer asli agar memberikan perilaku yang konsisten terlepas dari platform pengkompilasi.
Jika operasi menghasilkan nilai konstanta dalam 32-bit, pelipatan konstan dilakukan pada waktu kompilasi.
Jika tidak, operasi dijalankan pada runtime dan tidak dianggap sebagai konstanta.
Konversi
Ada konversi identitas antara nint
dan IntPtr
, dan antara nuint
dan UIntPtr
.
Ada konversi identitas antara jenis gabungan yang berbeda hanya dengan inti asli dan jenis yang mendasari: array, Nullable<>
, jenis terbangun, dan tuples.
Tabel di bawah ini mencakup konversi antara tipe khusus.
(IL untuk setiap konversi mencakup varian untuk konteks unchecked
dan checked
jika berbeda.)
Catatan umum pada tabel di bawah ini:
-
conv.u
adalah perpanjangan nol ke integer asli danconv.i
adalah perpanjangan tanda ke integer asli. - konteks
checked
untuk pelebaran dan penyempitan adalah:-
conv.ovf.*
untuksigned to *
-
conv.ovf.*.un
untukunsigned to *
-
- konteks
unchecked
untuk pelebaran adalah:-
conv.i*
untuksigned to *
(di mana * adalah lebar target) -
conv.u*
untukunsigned to *
(di mana * adalah lebar target)
-
-
unchecked
konteks untuk penyempitan adalah:-
conv.i*
untukany to signed *
(di mana * adalah lebar target) -
conv.u*
untukany to unsigned *
(di mana * adalah lebar target)
-
Mengambil beberapa contoh:
-
sbyte to nint
dansbyte to nuint
menggunakanconv.i
saatbyte to nint
danbyte to nuint
menggunakanconv.u
karena semuanya melebar. -
nint to byte
dannuint to byte
menggunakanconv.u1
saatnint to sbyte
dannuint to sbyte
menggunakanconv.i1
. Untukbyte
,sbyte
,short
, danushort
, "jenis tumpukan" adalahint32
. Jadi,conv.i1
secara efektif "dikonversi turun ke byte bertanda dan kemudian diperluas tandanya hingga int32" sementaraconv.u1
secara efektif "dikonversi turun ke byte tak bertanda dan kemudian diperluas dengan nol hingga int32". -
checked void* to nint
menggunakanconv.ovf.i.un
cara yang sama sepertichecked void* to long
menggunakanconv.ovf.i8.un
.
Operand | Target | Konversi | IL |
---|---|---|---|
object |
nint |
Unboxing | unbox |
void* |
nint |
PointerToVoid (Pointer ke Void) | nop / conv.ovf.i.un |
sbyte |
nint |
ImplisitNumerik | conv.i |
byte |
nint |
ImplisitNumerik | conv.u |
short |
nint |
ImplisitNumerik | conv.i |
ushort |
nint |
ImplisitNumerik | conv.u |
int |
nint |
ImplisitNumerik | 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 |
ImplisitNumerik | 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 |
Identitas | |
UIntPtr |
nint |
Tidak | |
object |
nuint |
Unboxing | unbox |
void* |
nuint |
PointerToVoid (Pointer ke Void) | nop |
sbyte |
nuint |
ExplicitNumeric | conv.i / conv.ovf.u |
byte |
nuint |
ImplisitNumerik | conv.u |
short |
nuint |
ExplicitNumeric | conv.i / conv.ovf.u |
ushort |
nuint |
ImplisitNumerik | conv.u |
int |
nuint |
ExplicitNumeric | conv.i / conv.ovf.u |
uint |
nuint |
ImplisitNumerik | conv.u |
long |
nuint |
ExplicitNumeric | conv.u / conv.ovf.u |
ulong |
nuint |
ExplicitNumeric | conv.u / conv.ovf.u.un |
char |
nuint |
ImplisitNumerik | 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 |
Tidak | |
UIntPtr |
nuint |
Identitas | |
Enumerasi | nint |
EnumerasiEksplisit (ExplicitEnumeration) | |
Enumerasi | nuint |
EnumerasiEksplisit (ExplicitEnumeration) |
Operand | Target | Konversi | IL |
---|---|---|---|
nint |
object |
Tinju | box |
nint |
void* |
PointerToVoid (Pointer ke Void) | nop / conv.ovf.u |
nint |
nuint |
ExplicitNumeric |
conv.u (dapat dihilangkan) / 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 |
ImplisitNumerik | conv.i8 |
nint |
ulong |
ExplicitNumeric | conv.i8 / conv.ovf.u8 |
nint |
char |
ExplicitNumeric | conv.u2 / conv.ovf.u2 |
nint |
float |
ImplisitNumerik | conv.r4 |
nint |
double |
ImplisitNumerik | conv.r8 |
nint |
decimal |
ImplisitNumerik | conv.i8 decimal decimal.op_Implicit(long) |
nint |
IntPtr |
Identitas | |
nint |
UIntPtr |
Tidak | |
nint |
Enumerasi | EnumerasiEksplisit (ExplicitEnumeration) | |
nuint |
object |
Tinju | box |
nuint |
void* |
PointerToVoid (Pointer ke Void) | nop |
nuint |
nint |
ExplicitNumeric |
conv.i (dapat dihilangkan) / 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 |
ImplisitNumerik | conv.u8 |
nuint |
char |
ExplicitNumeric | conv.u2 / conv.ovf.u2.un |
nuint |
float |
ImplisitNumerik | conv.r.un conv.r4 |
nuint |
double |
ImplisitNumerik | conv.r.un conv.r8 |
nuint |
decimal |
ImplisitNumerik | conv.u8 decimal decimal.op_Implicit(ulong) |
nuint |
IntPtr |
Tidak | |
nuint |
UIntPtr |
Identitas | |
nuint |
Enumerasi | EnumerasiEksplisit (ExplicitEnumeration) |
Konversi dari A
ke Nullable<B>
adalah:
- konversi implisit nullable jika ada konversi identitas atau konversi implisit dari
A
keB
; - konversi bertipe nullable eksplisit jika terdapat konversi eksplisit dari
A
keB
; - jika tidak, maka tidak valid
Konversi dari Nullable<A>
ke B
adalah:
- konversi eksplisit nullable jika ada konversi identitas atau konversi numerik implisit atau eksplisit dari
A
keB
; - jika tidak, maka tidak valid
Konversi dari Nullable<A>
ke Nullable<B>
adalah:
- konversi identitas jika ada konversi identitas dari
A
keB
; - konversi nullable eksplisit jika ada konversi numerik implisit atau eksplisit dari
A
keB
; - jika tidak, maka tidak valid
Operator
Operator yang telah ditentukan sebelumnya adalah sebagai berikut.
Operator ini dipertimbangkan selama resolusi kelebihan beban berdasarkan aturan normal untuk konversi implisit jika setidaknya salah satu operand berjenis nint
atau nuint
.
(IL untuk setiap operator mencakup varian untuk konteks unchecked
dan checked
jika berbeda.)
Unari | Tanda Tangan Operator | 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 |
Biner | Tanda Tangan Operator | 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 |
Untuk beberapa operator biner, operator IL mendukung jenis operand tambahan (lihat tabel jenis ECMA-335 III.1.5 Operand). Tetapi set jenis operand yang didukung oleh C# terbatas demi kesederhanaan dan untuk konsistensi dengan operator yang ada dalam bahasa ini.
Versi operator yang telah diangkat, dengan argumen dan jenis pengembalian berupa nint?
dan nuint?
, didukung.
Operasi penugasan gabungan x op= y
di mana x
atau y
adalah int bawaan mengikuti aturan yang sama seperti jenis primitif lainnya dengan operator yang sudah ditetapkan.
Secara khusus ekspresi terikat sebagai x = (T)(x op y)
di mana T
adalah jenis x
dan di mana x
hanya dievaluasi sekali.
Operator shift harus menutupi jumlah bit yang akan digeser - ke 5 bit jika sizeof(nint)
adalah 4, dan menjadi 6 bit jika sizeof(nint)
adalah 8.
(lihat §12.11) dalam spesifikasi C#).
Pengkompilasi C#9 akan melaporkan kesalahan yang mengikat ke operator bilangan bulat asli yang telah ditentukan saat mengkompilasi dengan versi bahasa yang lebih lama, tetapi akan memungkinkan penggunaan konversi yang telah ditentukan sebelumnya ke dan dari bilangan bulat asli.
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
}
}
Aritmatika penunjuk
Tidak ada operator yang telah ditentukan sebelumnya di C# untuk penambahan atau pengurangan pointer dengan offset bilangan bulat asli.
Sebagai gantinya, nilai nint
dan nuint
dipromosikan ke long
dan ulong
dan aritmatika pointer menggunakan operator yang telah ditentukan sebelumnya untuk jenis tersebut.
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)
Peningkatan numerik biner
promosi numerik biner teks informatif (lihat §12.4.7.3) dalam spesifikasi C# telah diperbarui sebagai berikut:
- …
- Jika tidak, jika salah satu operand berjenis
ulong
, operand lain dikonversi ke jenisulong
, atau kesalahan waktu pengikatan terjadi jika operand lain berjenissbyte
,short
,int
,nint
, ataulong
.- Jika tidak, jika salah satu operand berjenis
nuint
, operand lain dikonversi ke jenisnuint
, atau kesalahan waktu pengikatan terjadi jika operand lain berjenissbyte
,short
,int
,nint
, ataulong
.- Jika tidak, jika salah satu operan berjenis
long
, operand lainnya dikonversi ke jenislong
.- Jika tidak, jika salah satu operan berjenis
uint
dan operand lainnya berjenissbyte
,short
,nint
, atauint
, kedua operan dikonversi ke jenislong
.- Jika tidak, jika salah satu operan berjenis
uint
, operand lainnya dikonversi ke jenisuint
.- Jika tidak, jika salah satu operan berjenis
nint
, operand lainnya dikonversi ke jenisnint
.- Jika tidak, kedua operan dikonversi ke jenis
int
.
Dinamis
Konversi dan operator disintesis oleh pengkompilasi dan bukan bagian dari jenis IntPtr
dan UIntPtr
yang mendasar.
Akibatnya, konversi dan operator tersebut tidak tersedia dari pengikat waktu proses untuk 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'
Jenis anggota
Satu-satunya konstruktor untuk nint
atau nuint
adalah konstruktor tanpa parameter.
Anggota System.IntPtr
dan System.UIntPtr
berikut secara eksplisit dikecualikan dari nint
atau 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();
Anggota System.IntPtr
dan System.UIntPtr
yang tersisa secara implisit disertakan dalam dalam nint
dan nuint
. Untuk .NET Framework 4.7.2:
public override bool Equals(object obj);
public override int GetHashCode();
public override string ToString();
public string ToString(string format);
Antarmuka yang diimplementasikan oleh System.IntPtr
dan System.UIntPtr
secara implisit disertakan dalam nint
dan nuint
, dengan kemunculan tipe dasar digantikan oleh tipe bilangan bulat asli yang sesuai.
Misalnya jika IntPtr
menerapkan ISerializable, IEquatable<IntPtr>, IComparable<IntPtr>
, maka nint
menerapkan ISerializable, IEquatable<nint>, IComparable<nint>
.
Mengambil alih, menyembunyikan, dan mengimplementasikan
nint
dan System.IntPtr
, dan nuint
dan System.UIntPtr
, dianggap setara untuk mengambil alih, menyembunyikan, dan menerapkan.
Kelebihan beban tidak dapat berbeda dengan nint
dan System.IntPtr
, dan nuint
dan System.UIntPtr
, saja.
Penimpaan dan implementasi dapat berbeda hanya dengan nint
dan System.IntPtr
, atau nuint
dan System.UIntPtr
.
Metode menyembunyikan metode lain yang hanya berbeda pada nint
dan System.IntPtr
, atau nuint
dan System.UIntPtr
.
Lain-lain
ekspresi nint
dan nuint
yang digunakan sebagai indeks array dipancarkan tanpa konversi.
static object GetItem(object[] array, nint index)
{
return array[index]; // ok
}
nint
dan nuint
tidak dapat digunakan sebagai jenis dasar enum
dari C#.
enum E : nint // error: byte, sbyte, short, ushort, int, uint, long, or ulong expected
{
}
Pembacaan dan penulisan bersifat atomik untuk nint
dan nuint
.
Bidang dapat ditandai volatile
untuk jenis nint
dan nuint
.
ECMA-334 15.5.4 tidak menyertakan enum
dengan jenis dasar System.IntPtr
atau System.UIntPtr
.
default(nint)
dan new nint()
setara dengan (nint)0
; default(nuint)
dan new nuint()
setara dengan (nuint)0
.
typeof(nint)
adalah typeof(IntPtr)
; typeof(nuint)
adalah typeof(UIntPtr)
.
sizeof(nint)
dan sizeof(nuint)
didukung tetapi memerlukan kompilasi dalam konteks yang tidak aman (sebagaimana diperlukan untuk sizeof(IntPtr)
dan sizeof(UIntPtr)
).
Nilai bukan konstanta waktu kompilasi.
sizeof(nint)
diimplementasikan sebagai sizeof(IntPtr)
daripada IntPtr.Size
; sizeof(nuint)
diimplementasikan sebagai sizeof(UIntPtr)
daripada UIntPtr.Size
.
Diagnostik pengkompilasi untuk referensi tipe yang melibatkan nint
atau nuint
melaporkan nint
atau nuint
daripada IntPtr
atau UIntPtr
.
Metadata
nint
dan nuint
diwakili dalam metadata sebagai System.IntPtr
dan System.UIntPtr
.
Referensi tipe yang mencakup nint
atau nuint
dikeluarkan dengan System.Runtime.CompilerServices.NativeIntegerAttribute
untuk menunjukkan bagian mana dari referensi tipe yang merupakan integer asli.
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;
}
}
Pengodean referensi jenis dengan NativeIntegerAttribute
tercakup dalam NativeIntegerAttribute.md.
Alternatif
Alternatif untuk pendekatan "jenis penghapusan" di atas adalah memperkenalkan jenis baru: System.NativeInt
dan System.NativeUInt
.
public readonly struct NativeInt
{
public IntPtr Value;
}
Tipe berbeda akan memungkinkan kelebihan beban yang berbeda dari IntPtr
dan memungkinkan penguraian yang berbeda serta ToString()
.
Tetapi akan ada lebih banyak pekerjaan bagi CLR untuk menangani jenis ini secara efisien yang mengalahkan tujuan utama fitur - efisiensi.
Dan interoperabilitas dengan kode int asli yang ada yang menggunakan IntPtr
akan lebih sulit.
Alternatif lain adalah menambahkan lebih banyak dukungan int asli untuk IntPtr
dalam kerangka kerja tetapi tanpa dukungan kompilator tertentu.
Setiap konversi baru dan operasi aritmatika akan didukung oleh pengkompilasi secara otomatis.
Tetapi bahasa tidak akan menyediakan kata kunci, konstanta, atau operasi checked
.
Rapat desain
- 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