5

In C# 7.2, we saw the introduction of the in modifier for method parameters to pass read-only references to objects. I'm working on a new .NET Standard project using 7.2, and out of curiosity I tried compiling with the in keyword on the parameters for the equality operators for a struct.

i.e. - public static bool operator == (in Point l, in Point r)

not - public static bool operator == (Point l, Point r)

I was initially a bit surprised that this worked, but after thinking about it a bit I realized that there is probably no functional difference between the two versions of the operator. I wanted to confirm these suspicions, but after a somewhat thorough search around, I can't find anything that explicitly talks about using the in keyword in operator overloads.

So my question is whether or not this actually has a functional difference, and if it does, is there any particular reason to encourage or discourage the use of in with operator arguments. My initial thoughts are that there is no difference, particularly if the operator is inlined. However, if it does make a difference, it seems like in parameters should be used everywhere (everywhere that readonly references make sense, that is), as they provide a speed bonus, and, unlike ref and out, don't require the user to prepend the those keywords when passing objects. This would allow more efficient value-type object passing without a single change on the user of the methods and operators.

Overall, this may go beyond the sort of small-scale optimizations that most C# developers worry about, but I am curious as to whether or not it has an effect.

Sean M.
  • 113
  • 2
  • 7
  • `Point` is a value type, so `in` doesn't really make any difference here. – 500 - Internal Server Error Oct 12 '18 at 19:35
  • Is this because operators are generally inlined? Because normally this would make the difference of passing by reference instead of value. – Sean M. Oct 12 '18 at 19:38
  • 1
    @500-InternalServerError `in` is almost exclusively useful for value types. What makes you think that `in` is pointless when used with value types. – Servy Oct 12 '18 at 19:48
  • @SeanM. You appear to overestimate the benefits of passing a value by reference. Unless the value type is particularly large, someone else is mutating it *and you actually want to observe those mutations*, there isn't much to be gained. If your structs are well designed, that is to say, they are not significantly larger than the size of a reference, then passing through the reference will be comparable to copying the value. It's really only a win for poorly designed value types (or the very rare cases that are an exception to those guidelines). – Servy Oct 12 '18 at 19:54
  • I guess the guidelines are exactly the same as for regular methods: if the struct is sufficiently large then it makes sense to use "in". What "sufficiently large" means is another question. – jacekbe Oct 12 '18 at 19:57
  • I understand that `Point` (which in my case is just two `Int32`s) is probably a bad example, its just what I initially used in my code so I just copy/pasted it. In the case where the value types are significantly larger than the reference size, this would have more of an effect. I am more curious about the effects it would have in operators in general, if different from methods, due to the effects of inlining or other optimizations. – Sean M. Oct 12 '18 at 19:58

1 Answers1

0

whether or not this actually has a functional difference... My initial thoughts are that there is no difference, particularly if the operator is inlined

Since the operator == overload is invoked like a regular static method in MSIL, it has the functional difference. It can help to avoid unnecessary copying like in a regular method.

is there any particular reason to encourage or discourage the use of in with operator arguments.

According to this article it is recommended to apply in modifier when value types are larger than System.IntPtr.Size. But it is important that the value type should be readonly struct. Otherwise in modifier can harm the performance because the compiler will create a defensive copy when calling struct's methods and properties since they can change the state of the argument.

AlbertK
  • 11,841
  • 5
  • 40
  • 36