Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I foreach over an array of structs without copying the elements in C# 8?

Tags:

c#

ref

c#-8.0

With the new readonly instance member features in C# 8, I try to minify unnecessary copying of struct instances in my code.

I do have some foreach iterations over arrays of structs, and according to this answer, it means that every element is copied when iterating over the array.

I thought I can simply modify my code now to prevent the copying, like so:

// Example struct, real structs may be even bigger than 32 bytes.
struct Color
{
    public int R;
    public int G;
    public int B;
    public int A;
}

class Program
{
    static void Main()
    {
        Color[] colors = new Color[128];
        foreach (ref readonly Color color in ref colors) // note 'ref readonly' placed here
            Debug.WriteLine($"Color is {color.R} {color.G} {color.B} {color.A}.");
    }
}

This sadly does not compile with

CS1510  A ref or out value must be an assignable variable

However, using an indexer like this compiles:

static void Main()
{
    Color[] colors = new Color[128];
    for (int i = 0; i < colors.Length; i++)
    {
        ref readonly Color color = ref colors[i];
        Debug.WriteLine($"Color is {color.R} {color.G} {color.B} {color.A}.");
    }
}

Is my syntax in the foreach alternative wrong, or is this simply not possible in C# 8 (possibly because of how the enumeration is implemented internally)? Or is C# 8 applying some intelligence nowadays and does no longer copy the Color instances by itself?

like image 601
Ray Avatar asked Sep 23 '19 20:09

Ray


People also ask

Does forEach create a copy?

Modifying the array during iterationforEach() does not make a copy of the array before iterating.

How do you access an array in a struct?

1. Array elements are accessed using the Subscript variable, Similarly Structure members are accessed using dot [.] operator. Structure written inside another structure is called as nesting of two structures.

Is it possible to have an array of structs?

A structure may contain elements of different data types – int, char, float, double, etc. It may also contain an array as its member. Such an array is called an array within a structure. An array within a structure is a member of the structure and can be accessed just as we access other elements of the structure.

Do structs take up memory?

Answers. "is known that struct objects consume less memory than class objects because struct object does not need an additional memory location to store the memory address of the object created from the new operator." Yes, that's indeed a difference. For example if you have an array of 10 System.


1 Answers

foreach works based on the target type's definitions rather than some internal blackboxes. We could make use of this to create by-ref enumeration support:

//using System;

public readonly struct ArrayEnumerableByRef<T>
{
    private readonly T[] _target;

    public ArrayEnumerableByRef(T[] target) => _target = target;

    public Enumerator GetEnumerator() => new Enumerator(_target);

    public struct Enumerator
    {
        private readonly T[] _target;

        private int _index;

        public Enumerator(T[] target)
        {
            _target = target;
            _index = -1;
        }

        public readonly ref T Current
        {
            get
            {
                if (_target is null || _index < 0 || _index > _target.Length)
                {
                    throw new InvalidOperationException();
                }
                return ref _target[_index];
            }
        }

        public bool MoveNext() => ++_index < _target.Length;

        public void Reset() => _index = -1;
    }
}

public static class ArrayExtensions
{
    public static ArrayEnumerableByRef<T> ToEnumerableByRef<T>(this T[] array) => new ArrayEnumerableByRef<T>(array);
}

Then we could enumerate an array with foreach loop by reference:

static void Main()
{
    var colors = new Color[128];

    foreach (ref readonly var color in colors.ToEnumerableByRef())
    {
        Debug.WriteLine($"Color is {color.R} {color.G} {color.B} {color.A}.");
    }
}
like image 100
Alsein Avatar answered Sep 22 '22 16:09

Alsein