1

I am creating a matrix struct and am trying to add an indexer to the matrix like so:

public struct Vector4f
{
    public float X;
    public float Y;
    public float Z;
    public float W;
}

public struct Matrix4x4f
{
    public Vector4f X;
    public Vector4f Y;
    public Vector4f Z;
    public Vector4f W;

    public ref Vector4f this[int index]
    {
        get
        {
            return ref Unsafe.Add(ref X, index);
        }
    }
}

I am unable to get rid of the error in the getter however.

CS8347: Cannot use a result of 'Unsafe.Add(ref Vector4f, int)' in this context because it may expose variables referenced by parameter 'source' outside of their declaration scope.

I there a way to do what I am trying to do? The goal is to be able to write the following (while still using value types):

var m = new Matrix4x4f();
m[2].X = 3.14f;
Rick de Water
  • 2,388
  • 3
  • 19
  • 37
  • On another sidenote, shouldn't these structs be decorated with `[StructLayout(LayoutKind.Sequential)]` just to ensure that `Unsafe.Add` will always work as expected? – Ray Jun 20 '22 at 15:58
  • If you want a reference then you should make it a class instead of a struct. If you want to buy in to the possible speed advantages of value types then you should return a value. If you don't know what you want then you should profile either scenario, but be sure to do so with real-world usage. – Hans Passant Jun 20 '22 at 16:05

3 Answers3

1

This is probably related to CS8170 which is put in place to prevent references to fields of local / temporary instances to escape the scope in which they're valid. S. Why can't a C# struct method return a reference to a field, but a non-member method can?

As provided by an answer there, you can still go "full unsafe" by using this, but then it's your responsibility to not wreak havoc with references to instances that went out of scope.

public unsafe ref Vector4f this[int index]
{
    get
    {
        fixed (Vector4f* pX = &X)
            return ref *(pX + index);
    }
}
Ray
  • 7,940
  • 7
  • 58
  • 90
  • Just be careful using this, possibly only inline as in your example and never store the returned reference anywhere. It may be safer to give up on the indexer syntax and use other methods not requiring this kinda "hack". – Ray Jun 20 '22 at 21:56
1

You can achieve what you need by annotating the indexer (or its getter) with [System.Diagnostics.CodeAnalysis.UnscopedRef]:

[System.Diagnostics.CodeAnalysis.UnscopedRef]
public ref Vector4f this[int index]
{
    get
    {
        return ref Unsafe.Add(ref X, index);
    }
}

This way, you don't need to use the unsafe context.

While normally instance methods of structs are not allowed to return a reference to this (or any member of this), the annotation overrides this behavior and makes returning a reference to this possible.

This part of the documentation on low level struct improvements in C# 11 contains more information on [UnscopedRef].

Bartosz
  • 461
  • 2
  • 9
0

You can add another indexer property with two parameters for access to vector components:

public double this[int index1, int index2]
{
    get
    {
            switch (index1)
            {
                case 0: return X[index2];
                case 1: return Y[index2];
                case 2: return Z[index2];
                case 3: return W[index2];
            }
            throw new ArgumentOutOfRangeException(nameof(index1));
    }
    set
    {
            switch (index1)
            {
                case 0: X[index2] = value; break;
                case 1: Y[index2] = value; break;
                case 2: Z[index2] = value; break;
                case 3: W[index2] = value; break;
            }
            throw new ArgumentOutOfRangeException(nameof(index1));
        }
    }
}
Victor
  • 2,313
  • 2
  • 5
  • 13