C# tutorials > Modern C# Features > C# 6.0 and Later > What are native sized integers (`nint`, `nuint`)?

What are native sized integers (`nint`, `nuint`)?

C# 9.0 introduced nint and nuint, which are native-sized integers. This means their size depends on the underlying platform's architecture (32-bit or 64-bit). Understanding these types is crucial for interoperability with native libraries and optimizing performance in certain scenarios.

Definition and Basic Usage

nint represents a signed native-sized integer, and nuint represents an unsigned native-sized integer. The sizeof operator returns the size (in bytes) of the type. The size will be 4 bytes on a 32-bit platform and 8 bytes on a 64-bit platform. The example also shows how to check if your application is running on a 64-bit process.

using System;

public class NativeIntegersExample
{
    public static void Main(string[] args)
    {
        nint nativeInt = 10;
        nuint nativeUInt = 20;

        Console.WriteLine($"Native Int: {nativeInt}");
        Console.WriteLine($"Native UInt: {nativeUInt}");
        Console.WriteLine($"Size of nint: {sizeof(nint)}");
        Console.WriteLine($"Size of nuint: {sizeof(nuint)}");

        // Check if the platform is 64-bit
        if (Environment.Is64BitProcess)
        {
            Console.WriteLine("Running on a 64-bit process.");
        }
        else
        {
            Console.WriteLine("Running on a 32-bit process.");
        }
    }
}

Concepts Behind Native-Sized Integers

The primary purpose of nint and nuint is to interoperate with native APIs that use platform-specific integer sizes. Many native libraries use IntPtr and UIntPtr, which have been around since .NET 1.0. nint and nuint are designed to be CLS-compliant equivalents of IntPtr and UIntPtr respectively, providing better type safety and language integration.

Real-Life Use Case: Interoperability with Native Libraries

A common use case for native-sized integers is when you're interacting with unmanaged code via Platform Invoke (P/Invoke). In this example, we're using DllImport to call the GetCurrentProcessId function from kernel32.dll. The return type is declared as nint, ensuring it matches the platform's native integer size. This is a contrived example; in more complex cases the native code might expect an integer pointer. Without nint and nuint, you would need to use IntPtr and UIntPtr and potentially perform casts, reducing type safety.

// Example illustrating interaction with native code (P/Invoke)
using System;
using System.Runtime.InteropServices;

public class NativeInterop
{
    [DllImport("kernel32.dll")]
    public static extern nint GetCurrentProcessId();

    public static void Main(string[] args)
    {
        nint processId = GetCurrentProcessId();
        Console.WriteLine($"Current Process ID: {processId}");
    }
}

When to Use Them

Use nint and nuint primarily when interacting with native libraries that rely on platform-specific integer sizes. They are also beneficial when working with large arrays or memory regions where the index needs to accommodate the maximum possible memory address size on the platform. They should be preferred over IntPtr and UIntPtr in new code. Don't use them as general-purpose integers; stick to int, long, etc., unless you have a specific reason to use native-sized integers.

Memory Footprint

The memory footprint of nint and nuint depends on the platform. On a 32-bit platform, they are 4 bytes, and on a 64-bit platform, they are 8 bytes. Be mindful of this when designing data structures, particularly when dealing with large collections. Using int when a native-sized integer is not needed can save memory on 64-bit systems.

Alternatives

Before C# 9.0, the alternatives were IntPtr and UIntPtr. These types are still available, but nint and nuint are preferred due to better type safety and integration with the C# language. For general-purpose integers, int, long, uint, and ulong are the standard choices. If you don't need native sized integers stick with these standard types.

Pros

  • Improved interoperability with native code.
  • Better type safety compared to IntPtr and UIntPtr.
  • Automatic adaptation to the platform's integer size.

Cons

  • Platform-dependent size can introduce subtle bugs if not handled carefully.
  • Increased memory footprint on 64-bit systems compared to int if not strictly necessary.
  • Potential confusion for developers unfamiliar with the concept of native-sized integers.

Best Practices

  • Use nint and nuint only when necessary for native interoperability or when dealing with memory addresses.
  • Always be aware of the platform's architecture (32-bit or 64-bit) when working with native-sized integers.
  • Avoid using them for general-purpose integer arithmetic unless there's a specific reason.
  • Consider the memory footprint implications, especially when dealing with large collections.

Interview Tip

When asked about nint and nuint in an interview, explain that they are native-sized integers introduced in C# 9.0 to improve interoperability with native libraries and provide better type safety than IntPtr and UIntPtr. Be sure to mention their platform-dependent size and the scenarios where they are most appropriate. Also, mention the memory footprint considerations.

FAQ

  • Are `nint` and `nuint` CLS-compliant?

    Yes, nint and nuint are CLS-compliant, which means they can be used in code that interoperates with other .NET languages.

  • Can I use `nint` and `nuint` in mathematical operations?

    Yes, you can use nint and nuint in mathematical operations, but be mindful of potential overflow issues, especially when performing operations between native-sized integers and regular integers.

  • How do `nint` and `nuint` relate to `IntPtr` and `UIntPtr`?

    nint and nuint are CLS-compliant equivalents to IntPtr and UIntPtr. They offer better type safety and language integration and should be preferred in new code. The underlying representation is the same: a pointer sized integer that matches the target architecture.