For classes,
ref
generally doesn't do a whole lot performance-wise. It's equivalent to an alias or reference from C++. So for
void foo(ref SomeClass s)
, instead of
s
being a copy of the referential argument, it is the argument. For example:
void foo(ref SomeClass s) { s = new SomeClass(); }
void foo2(SomeClass s) { s = new SomeClass(); }
SomeClass x = new SomeClass();
foo(ref x);
foo2(x);
Since copying pointers isn't exactly a bottleneck in the overwhelming majority of programs,
ref
is more about flexibility than performance when dealing with classes.
For structs, as you've already noted, it avoids the copy of the struct. A struct doesn't necessarily have to be large for this to be useful for performance. For example, imagine a large, tight loop that passes a relatively small struct to a function. Even worse, imagine that function can't be inlined and updates the struct by returning it. That's 3 copies per call - argument, return, assignment of return. Using
ref
you could avoid all three copies.
I think you are also disregarding Griff's post a bit too quickly. What he's hinting at is that you can't always just make a struct a class instead. They are fundamentally different types that are allocated differently with different properties and assumptions that the compiler makes about them. Stack vs main memory (both allocation and access), cleanup, inheritance, etc all factor into performance and optimizations.
Docs:
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/ref-returns[
^]