0

I am guessing the answer will eventually be "no, you can't do that", but trying to figure out how to do a thing in C#.

I am wondering on a bit of glue code between two pieces that I do not have any control over. On the data side I have something like this:

public struct StructA
{
    public float varA;
}

public class ClassA
{
    public struct StructB
    {
        public StructA? varB;
    }

    private StructB? varC;
}

And then finally I have this function:

public static void FunctionA(ref float value) 

This is a function that I do not control, which wants a reference to a float. If I have a StructA and pass StructA.varA into FunctionA, it correctly operates on varA in place.

I have a float nested down in two nullable structs, and a function that will only take a reference to a float. I've tried using reflection to get the field info for the value inside the nullable wrapper, but it is behaving like GetValue is returning, well, the whole struct instead of a reference.

The only next step I can think of is trying something with an unsafe block and some explicit casting? I keep hitting half solutions where the compiler admits it knows what I am trying to do, but will not let me - is there some way to tell it to just do it?

(adding)

An example of how I've been trying to approach it:

StructA exampleA = (StructA)typeof(StructA?).GetField("value",BindingFlags.NonPublic|BindingFlags.Instance).GetValue(SomeNullableStructA);

I've also tried

unsafe
{
   StructA* ptr = (StructA*)&SomeNullableStructA;
}

Which is not taking into acount Nullable.hasValue, so I would not expect that to line up, but I didn't go too far down this path due to the error:

C8370 : Feature 'unmanaged constructed types' is not available in C# 7.3. Please use language version 8.0 or greater.

Since this is a dll loading into someone else's app, and it wants .NET framework 4.7.2, I am assuming I can not mix them.

17
  • 2
    Do you really need to operate on varA in place, or would it be sufficient to get it, operate on it, and set it back? Commented May 26 at 4:11
  • 3
    @NathanWeyer Please edit your question to include the source code you use (or try to use) to access/edit the field you want. Currently you have only shown two classes/structs. You also talk about a GetValue() method but your classes/structs does not have such method. Commented May 26 at 5:52
  • 1
    we need bit more information on how you attempt to read/write varB. It's pretty hard to get that from your text. Please share some basic sample-code that shows what exactly you're doing. Commented May 26 at 6:14
  • 1
    @Charlieface - the problem with your approach is that, even if you mutate ca.varC.Value.varA, the field ca.varC will still be null. That's because Nullable<T> contains two fields, internal T value; and private bool hasValue; and, while you have mutated the contents of varC.Value, ca.varC.hasValue is still false, see dotnetfiddle.net/u8LyOG. Commented May 26 at 15:58
  • 1
    In fact I don't think it's possible to reliably mutate the internal value of a Nullable<T> struct without considering the hasValue bool, so the querent really can't do what they want in a single call. Somehow they must either get, modify, and set the containing nullable, or mutate the hasValue as well. Commented May 26 at 16:00

1 Answer 1

0

You can use TypedReference for this. It is designed to hold a ref to an arbitrary location, kind of like a generic ref T, except with reflection. You can also pass around arbitrary refs in this way. They are fully tracked by GC and are not unsafe.

You pass to the constructor the top-level containing class's FieldInfo and all respective FieldInfos until the struct you want.

Note that a SomeStruct? is actually Nullable<T> under the hood. Note also the use of the undocumented __refvalue instruction, which corresponds to the refanyval IL instruction.

var ca = new ClassA();
var typedRef = TypedReference.MakeTypedReference(ca, new FieldInfo[]{
    typeof(ClassA).GetField("varC", BindingFlags.Instance | BindingFlags.NonPublic),
    typeof(ClassA.StructB?).GetField("value", BindingFlags.Instance | BindingFlags.NonPublic),
    typeof(ClassA.StructB).GetField("varB", BindingFlags.Instance | BindingFlags.Public),
    typeof(StructA?).GetField("value", BindingFlags.Instance | BindingFlags.NonPublic)
});
ref StructA sa = ref __refvalue(typedRef, StructA);
FunctionA(ref sa.varA);

Caveats:

  • You can't get a TypedReference to any primitive type, so this isn't going to work if the bottom-level StructA is a private struct type which you can't access using ref, as you can't get a TypedReference to a float.
    Conveniently, this doesn't matter in this case because StructA is public.
  • You can't get a TypedReference to a struct contained in a static field, as it needs an actual object as the first parameter, it won't accept null.

dotnetfiddle

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.