36

In my C# application I receive a pointer to a C++ struct in callback/delegate. I'm not sure if class can do the trick, but just casting a C++ pointer to an appropriate C# struct works fine, so I'm using a C# struct for storing data.

Now I want to pass a reference to the struct for further processing.

  • I can't use class because it probably will not "map" perfectly to the C++ struct.
  • I don't want to copy the struct for better latency.

How can I do that?


This example demonstrates that struct is passed by value, not by reference:

using System; namespace TestStruct { struct s { public int a; } class Program { static void Main(string[] args) { s s1 = new s { a = 1 }; Foo(s1); Console.WriteLine("outer a = " + s1.a); } private static void Foo(s s1) { s1.a++; Console.WriteLine("inner a = " + s1.a); } } } 

Output is:

inner a = 2 outer a = 1 
3
  • 6
    Beware of your reasoning about struct: struct in C++ is exactly the same as class (barring default accessibility), while in C# they are completely different - value and reference types. Chances are you really would be better of with classes in C# - at very least read and understand how struct behave in C#. Commented May 17, 2013 at 17:27
  • A large number of my interop classes are indeed classes and not structs. If you set up the marshalling correctly (and often the default marshalling works) then you can use a class. If in doubt, I try with a class first. Commented May 17, 2013 at 17:47
  • Mutable structs are evil. Commented Mar 8, 2023 at 0:17

2 Answers 2

63

It sounds like you just want to use ref to pass the struct by reference:

private static void Foo(ref s s1) { s1.a++; Console.WriteLine("inner a = " + s1.a); } 

And at the call site:

Foo(ref s1); 

See my article on parameter passing in C# for more details.

Note that other than for interop, I would normally strongly recommend against using mutable structs like this. I can understand the benefits here though.

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

3 Comments

+1. Side note: code handling struct must be written very carefully since almost all operations with struct create copy (except array, where you still can take a ref of an element).
Why do you strongly recommend against passing structs by ref? As far as I can tell, it's the same thing as passing by reference in C++ (eg. std::string&), and it can eliminate needless copying of potentially large structs.
@cmbasnett: I strongly recommended against using mutable structs. Passing a struct by ref is okay, but a) I would advise against large structs in general; b) I would argue against mutable structs (as do the Microsoft design guidelines) as they can cause a number of surprises.
1

You can use c# 7.2 in keyword as follows:

static float Sample(in Vector3 v) { // v.X = 2; // <-- this generate follow compiler error // error CS8332: Cannot assign to a member of variable 'v' // or use it as the right hand side of a ref assignment // because it is a readonly variable return v.X; } 

This ensures that struct argument v is:

  • readonly
  • passed by ref

IL details

Vector3 v = Vector3.One; float Sample(Vector3 v) { return v.X; } System.Console.WriteLine(Sample(v)); float ReadonlySample(in Vector3 v) { return v.X; } System.Console.WriteLine(ReadonlySample(v)); 

produce follow IL:

// Vector3 v2 = Vector3.One; IL_0001: call valuetype [System.Numerics.Vectors]System.Numerics.Vector3 [System.Numerics.Vectors]System.Numerics.Vector3::get_One() IL_0006: stloc.0 // Console.WriteLine(Sample(v2)); IL_0007: nop IL_0008: ldloc.0 IL_0009: call float32 test_console.Sample::'<Main>g__Sample|0_0'(valuetype [System.Numerics.Vectors]System.Numerics.Vector3) IL_000e: call void [System.Console]System.Console::WriteLine(float32) // (no C# code) IL_0013: nop // Console.WriteLine(ReadonlySample(in v2)); IL_0014: nop IL_0015: ldloca.s 0 IL_0017: call float32 test_console.Sample::'<Main>g__ReadonlySample|0_1'(valuetype [System.Numerics.Vectors]System.Numerics.Vector3&) IL_001c: call void [System.Console]System.Console::WriteLine(float32) 

you can see that using in we have ldloca in place of ldloc.

In short the struct is passed like it was a ref but is protected to writes by the compiler thanks to the in.

1 Comment

When I write the post I missed to insert this reference ; you can try yourself simply by changing language version in csproj, for example by setting <LangVersion>6</LangVersion> will produce following error for the same above code error CS8059: Feature 'readonly references' is not available in C# 6. Please use language version 7.2 or greater.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.