Razzupaltuff
Razzupaltuff

Reputation: 2301

Access members of nullable ValueTuple?

To access ValueTuple members, I would do

public Plane Init (ValueTuple<Vector4D, Vector4D, Vector4D, Vector4D> vertices)
{
    Vector4D[] m_vertices = new Vector4D [4];
    m_vertices[0] = vertices.Item1;
    // etc
    return this;
}

Can I use a nullable ValueTuple argument, and how would I access its members? Here's what I tried, and it doesn't work:

public Plane Init (ValueTuple<Vector4D, Vector4D, Vector4D, Vector4D>? vertices = null)
{
    Vector4D[] m_vertices = new Vector4D [4];
    // not working
    m_vertices[0] = vertices ?? vertices.Item1 : CVector4D (0,0,0,0);
    // not working either
    m_vertices[0] = vertices ?? vertices?.Item1 : CVector4D (0,0,0,0);
    // etc
    return this;
}

Upvotes: 0

Views: 429

Answers (4)

Olivier Jacot-Descombes
Olivier Jacot-Descombes

Reputation: 112259

By using the concise C# syntax for ValueTuple types and values, we can write:

static readonly Vector4D Origin4D = new Vector4D(0, 0, 0, 0);

public Plane Init((Vector4D, Vector4D, Vector4D, Vector4D)? vertices)
{
    var v = vertices ?? (Origin4D, Origin4D, Origin4D, Origin4D);
    var m_vertices = new Vector4D[] { v.Item1, v.Item2, v.Item3, v.Item4 };

    // etc
    return this;
}

If you are in control of Vector4D it is a good idea to add it a static member

public static Vector4D Origin { get; } = new Vector4D(0, 0, 0, 0);

You can then access it with Vector4D.Origin.


If you need a variable number of parameters, use a params parameter:

public Plane Init(params Vector4D[] vertices)
{
    Vector4D[] m_vertices = vertices;
}

Upvotes: 1

Marc Gravell
Marc Gravell

Reputation: 1062492

In the case where the null scenario would give the zero-default equivalent (if we assume that CVector4D is a struct), then there is a cheat you can use to simplify this code:

public Plane Init (ValueTuple<CVector4D, CVector4D, CVector4D, CVector4D>? vertices = null)
{
    var val = vertices.GetValueOrDefault();
    m_vertices = new[] { val.Item1, val.Item2, val.Item3, val.Item4 };
    // ...
}

This works because GetValueOrDefault() will return the inner T value of the T? if there is a value, and the default(T) otherwise; and in this case: default(T) will be the all-zero case that you wanted. So: all done.

But: you can probably simplify a bit more, and lose the T?:

public Plane Init (ValueTuple<CVector4D, CVector4D, CVector4D, CVector4D> vertices = default)
{
    m_vertices = new[] { vertices.Item1, vertices.Item2, vertices.Item3, vertices.Item4 };
    // ...
}

That said: it may be clearer to pass in the vectors individually rather than as a value-tuple, i.e.

public Plane Init (CVector4D vertex0, CVector4D vertex1, CVector4D vertex2, CVector4D vertex3)
{
    m_vertices = new[] { vertex0, vertex1, vertex2, vertex3 };
    // ...
}

Upvotes: 0

Orace
Orace

Reputation: 8359

To access an item for a Nullable<ValueTuple<>> you should use the Null-conditional operators ?. operator, and the null-coalescing operator ?? to provide a value in case of a null :

var v1 = vertices?.Item1 ?? fallBackValue;

But to avoid multiple redundant null test, you should use a default value for the tuple if it is null :

private static Vector4D ZeroVector4D { get; } = new Vector4D();
private static (Vector4D, Vector4D, Vector4D, Vector4D) DefaultVertices { get; } = (ZeroVector4D, ZeroVector4D, ZeroVector4D, ZeroVector4D);

public Plane Init(ValueTuple<Vector4D, Vector4D, Vector4D, Vector4D>? vertices = null)
{
    return Init(vertices ?? DefaultVertices);
}

public Plane Init(ValueTuple<Vector4D, Vector4D, Vector4D, Vector4D> vertices)
{
    m_vertices[0] = v.Item1;
    m_vertices[1] = v.Item2;
    m_vertices[2] = v.Item3;
    m_vertices[3] = v.Item4;

    return this;
}

You can also use some pattern matching :

var m_vertices = new Vector4D[4];
if (vertices is (Vector4D a, Vector4D b, Vector4D c, Vector4D d))
{
    m_vertices[0] = a;
    m_vertices[1] = b;
    m_vertices[2] = c;
    m_vertices[3] = d;
}

Upvotes: 1

Razzupaltuff
Razzupaltuff

Reputation: 2301

I found a solution that comes closest to what I want:

public Plane Init (ValueTuple<CVector4D, CVector4D, CVector4D, CVector4D>? vertices = null)
{
    CVector4D[] m_vertices;
    m_vertices = new Vector4D[4];
    // the elements of m_vertices are default initialized as intended already here
    if (vertices != null)
    {
        m_vertices[0] = vertices?.Item1;
        m_vertices[1] = vertices?.Item2;
        m_vertices[2] = vertices?.Item3;
        m_vertices[3] = vertices?.Item4;
    }
    return this;
}

It is just a little guesswork and a heck of a lot of googling to find out how to write this down.

Btw,

m_vertices[0] = vertices?.Item1 ?? vertices?.Item1 : new Vector4D (0,0,0,0);

Does not work; Instead of ":", I get an error that ";" or "}" are expected.

Simply writing

m_vertices[0] = vertices.Item1;

after the "if (vertices != null)" statement yields an error as well: (Vector4D, Vector4D, Vector4D, Vector4D) does not contain a definition for 'Item1' and no accessible extension method 'Item1' accepting a first argument of type '(Vector4D, Vector4D, Vector4D, Vector4D)' could be found.

Using target system ".NET Framework 4.7.2".

With the hint of Orace below, this is probably the (or a) right way to do it:

public Plane Init (ValueTuple<CVector4D, CVector4D, CVector4D, CVector4D>? vertices = null)
{
    CVector4D[] m_vertices;
    m_vertices = new Vector4D[4];
    // the elements of m_vertices are default initialized as intended already here
    if (vertices != null)
    {
        m_vertices[0] = vertices.Values.Item1;
        m_vertices[1] = vertices.Values.Item2;
        m_vertices[2] = vertices.Values.Item3;
        m_vertices[3] = vertices.Values.Item4;
    }
    return this;
}

Upvotes: 0

Related Questions