0

I'm using a method from a legacy library: F (ref T t) where T: struct. It's declared as ref for performance reasons only and does not modify the data it receives. This library cannot be altered.

In my code I have a new method G (in T t) where T: struct that calls F.

Is there a way to call F directly with the reference I receive without copying it to a temporary first?

chase
  • 1,623
  • 1
  • 16
  • 21
  • The way would be to make your function also a ref one. Then you would just hand the reference through all the way. – Christopher Dec 05 '18 at 15:14
  • In, ref and out just differ in their statement of intent: "The in keyword causes arguments to be passed by reference. It is like the ref or out keywords, except that in arguments cannot be modified by the called method. Whereas ref arguments may be modified, out arguments must be modified by the called method, and those modifications are observable in the calling context." in would have be the right tool here all along. But lacking that, you propably have to use ref for it too. The compiler not allow you to put a in aragument on a ref argument spot is exactly why in exists. – Christopher Dec 05 '18 at 15:16

2 Answers2

3

Yes, there is a way (but it uses the unsafe black magic).

First the disclaimer.

The fact that the method F does not modify the struct is only your "convention". For the C# compiler, a struct provided by ref is perfectly mutable.

Having a struct provided by readonly ref via in tells the compiler: please ensure that this struct cannot be mutated.

By the way, if you pass a struct as in, you have to ensure that this struct is declared as a readonly struct. Otherwise, the compiler will create defensive copies of the struct (read here for details.) This is the second reason why you normally cannot pass a readonly struct reference to a method accepting a struct by ref and mutating it.

If you still want to work around all those restrictions, you can use the System.Runtime.CompilerServices.Unsafe NuGet package.

There is a method in the Unsafe static class that can help you:

public static ref T AsRef<T>(in T source);

Here is an example:

void F<T>(ref T t) where T : struct
{
}

void G<T>(in T t) where T : struct
{
    F(ref System.Runtime.CompilerServices.Unsafe.AsRef(in t));
}
dymanoid
  • 14,771
  • 4
  • 36
  • 64
  • I'll look into the package, thanks. This, however, is incorrect: "cannot pass a readonly struct reference to a method accepting a struct by ref". You very much can do it, and there's no need for a defensive copy, since you can't actually modify it within a `ref T` method, since it's readonly. – chase Dec 05 '18 at 16:32
  • @chase, unhappy wording, refined. What I mean is: if you get it via `in`, then it's probably `readonly`, so passing it to a method accepting a `ref` might be dangerous, since that method might mutate the `struct`. – dymanoid Dec 05 '18 at 17:00
  • That's a very useful package indeed. Performance with `AsRef` seems identical to a direct call. Also, no more Emit, yay! – chase Dec 05 '18 at 17:07
  • I guess my "can't actually modify it" could also use a disclaimer: not by conventional means. With unsafe and reflection and whatnot all that goes out the window, as this package wonderfully demonstrates ;) – chase Dec 05 '18 at 17:17
-2

I just read up on "in". Apparently both in, ref and out are a form of call by reference. However the major difference is "their statement of intent".

in arguments can not be modified. They are only there by reference for peformance reason, if at all.

ref arguments may or may not be modified. It is the closest to classical "call by reference". Indeed it is the only way you got when you are implementing it via naked pointers

out arguments have to be modified; this can be relevant if a value needs initialisation (like a readonly in a constructor) - if it is handed in as a out variable, the compiler can rest assured it will be set. The other functions compiler made sure of that.

In a way in is a sort of "ref, but readonly". Being unable to assign something your received as "in" to a "ref" or "out" as well as blocking you from accidentally changing the value. I guess you will not get around either using a ref there too (wich makes this propably pointless) or doing the copy.

Christopher
  • 9,634
  • 2
  • 17
  • 31
  • 2
    I'm sorry, but this is a generic answer and not very helpful. I'm aware of what the keywords do. I'm looking for a way to circumvent normal operation, hopefully with some clever typecasting or some such instead of using Emit to do so (which is what I'm currently doing). – chase Dec 05 '18 at 16:13