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
Ola Ström
  • 4,136
  • 5
  • 22
  • 41
Oleg Vazhnev
  • 23,239
  • 54
  • 171
  • 305
  • 4
    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#. – Alexei Levenkov May 17 '13 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. – Matthew Watson May 17 '13 at 17:47
  • [Mutable structs are evil.](https://stackoverflow.com/q/441309/380384) – John Alexiou Mar 08 '23 at 00:17

2 Answers2

59

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.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 9
    +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). – Alexei Levenkov May 17 '13 at 17:25
  • 2
    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. – Colin Basnett Jun 21 '15 at 21:05
  • 1
    @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. – Jon Skeet Jun 21 '15 at 21:20
0

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.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
Lorenzo Delana
  • 532
  • 5
  • 11
  • 1
    When I write the post I missed to insert [this](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-7.2/readonly-ref) reference ; you can try yourself simply by changing language version in csproj, for example by setting `6` 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.` – Lorenzo Delana Mar 08 '23 at 08:18