C# tutorials > Modern C# Features > C# 6.0 and Later > What are inline arrays in C# 12 and when might you use them?

What are inline arrays in C# 12 and when might you use them?

Inline Arrays in C# 12: Enhancing Performance and Interoperability

C# 12 introduces a powerful new feature called inline arrays. This feature aims to improve performance in specific scenarios, especially when dealing with interop (interacting with native code) and high-performance data structures. This tutorial explores inline arrays, their purpose, syntax, and practical applications.

Understanding Inline Arrays

Inline arrays are a way to create arrays where the array elements are laid out contiguously in memory directly within a structure. This is different from regular C# arrays, which are reference types and reside on the heap. Inline arrays are value types, providing a more direct memory representation.

This direct memory layout can offer performance benefits by reducing indirection and improving cache locality, particularly when interacting with unmanaged code or working with large datasets where memory layout is critical.

Syntax and Declaration

To define an inline array, you need to use the [InlineArray] attribute from the System.Runtime.CompilerServices namespace. This attribute specifies the size of the array. The array elements are then implicitly available as fields within the struct.

Key points:

  • The [InlineArray] attribute takes an integer argument, representing the array size.
  • The struct must contain a single private field. This is the backing store for the array elements. The name of this field is arbitrary (in the above example, _element0), but it must exist.
  • Elements are accessed using the standard array indexer syntax (e.g., array[0]).

using System.Runtime.CompilerServices;

[InlineArray(10)]
public struct MyInlineArray
{
    private int _element0; // This field is REQUIRED.  Name is arbitrary.
}

public class Example
{
    public static void Main(string[] args)
    {
        MyInlineArray array = new MyInlineArray();
        array[0] = 10;
        array[5] = 50;
        Console.WriteLine(array[0]); // Output: 10
        Console.WriteLine(array[5]); // Output: 50
    }
}

Concepts Behind the Snippet

The provided code demonstrates the fundamental concept of inline arrays. The MyInlineArray struct declares an inline array of 10 integers. The _element0 field serves as the first element and backing store. The [InlineArray(10)] attribute informs the compiler to treat this struct as an inline array with a size of 10.

The code then shows how to access and modify the elements of the inline array using the indexer. This allows working with the inline array in a way that's syntactically similar to regular arrays.

Real-Life Use Case Section

A common use case for inline arrays is representing fixed-size data structures, such as vectors or matrices, especially in graphics or physics engines. The example above shows how to define a Vector3 struct using an inline array of three floats. This avoids the overhead of creating a separate array object on the heap and improves memory locality, which can be crucial for performance in these types of applications.

This is also useful in interoperability scenarios. Imagine you have a C library with a function that expects a pointer to an array of 3 floats representing a vector. With inline arrays, you can directly pass a Vector3 struct to that function without needing to copy the data into a separate managed array.

// Example: Representing a fixed-size vector
using System.Runtime.CompilerServices;

[InlineArray(3)]
public struct Vector3
{
    private float _element0;

    public Vector3(float x, float y, float z)
    {
        this[0] = x;
        this[1] = y;
        this[2] = z;
    }

    public float X { get { return this[0]; } set { this[0] = value; } }
    public float Y { get { return this[1]; } set { this[1] = value; } }
    public float Z { get { return this[2]; } set { this[2] = value; } }

    public override string ToString()
    {
        return $"({X}, {Y}, {Z})";
    }
}

public class Example
{
    public static void Main(string[] args)
    {
        Vector3 v = new Vector3(1.0f, 2.0f, 3.0f);
        Console.WriteLine(v); // Output: (1, 2, 3)
        v.X = 5.0f;
        Console.WriteLine(v); // Output: (5, 2, 3)
    }
}

Best Practices

  • Use with Caution: Inline arrays are a specialized feature. Use them only when performance profiling indicates a real benefit in terms of memory footprint or speed.
  • Keep it Small: Inline arrays are most effective when dealing with small, fixed-size arrays. Larger arrays might negate the benefits due to increased struct size.
  • Consider Immutability: If possible, design your inline array structs to be immutable to avoid unintended side effects and improve thread safety.
  • Interop Scenarios: Inline arrays are particularly well-suited for interoperability with unmanaged code, where you need to represent data structures that match the layout expected by native libraries.

Interview Tip

When discussing inline arrays in an interview, emphasize their purpose for performance optimization, especially in scenarios involving unmanaged code or low-latency applications. Be prepared to explain their memory layout benefits and the tradeoffs involved in using them. Mention their introduction in C# 12 and their use cases in game development or scientific computing.

When to Use Them

Consider using inline arrays when:

  • You need to represent a small, fixed-size collection of primitive types.
  • You are interoperating with unmanaged code that expects data in a specific memory layout.
  • You are working on a performance-critical application where memory layout and access speed are paramount.
  • You have identified a performance bottleneck related to array access and object allocation through profiling.

Memory Footprint

The memory footprint of an inline array is determined by the size of the underlying element type multiplied by the array size. For example, a [InlineArray(10)] struct MyInlineArray { private int _element0; } will occupy 40 bytes of memory (10 elements * 4 bytes per int). This is typically more compact than a regular C# array of 10 integers, because that array is a reference type that points to data stored on the heap, along with the overhead associated with the array object itself.

Alternatives

Alternatives to inline arrays include:

  • Regular C# arrays: Suitable for general-purpose array usage, especially when the array size is not fixed or when you need the flexibility of a reference type.
  • System.Span<T> and System.Memory<T>: Provide a way to work with contiguous regions of memory without creating new arrays. Useful for slicing arrays and working with memory buffers. These can wrap existing arrays.
  • Structs with individual fields: For very small collections (e.g., 2 or 3 elements), you might consider using individual fields instead of an inline array, although this approach becomes less manageable as the number of elements increases.

Pros

  • Improved performance: Reduced indirection and better cache locality can lead to faster access times.
  • Direct memory layout: Facilitates interoperability with unmanaged code.
  • Value type semantics: Avoids the overhead of heap allocation and garbage collection.
  • Compact memory footprint: Can be more memory-efficient than regular C# arrays, especially for small, fixed-size arrays.

Cons

  • Limited flexibility: Array size is fixed at compile time.
  • Struct size considerations: Large inline arrays can increase the size of the struct, potentially impacting performance.
  • Requires C# 12: Not available in earlier versions of C#.
  • Less familiar syntax: Requires understanding the [InlineArray] attribute and the underlying memory layout.

FAQ

  • Are inline arrays reference types or value types?

    Inline arrays are value types (structs).
  • What happens if I try to access an element outside the bounds of an inline array?

    You will get an IndexOutOfRangeException, just like with regular C# arrays.
  • Can I use inline arrays with generic types?

    Yes, you can use inline arrays with generic types, but the element type must be known at compile time.
  • Is the private field `_element0` mandatory?

    Yes, the private field that is the backing store for the inline array is mandatory. The name is arbitrary, but it must exist.