如何通过引用传递C#结构体?

36
在我的C#应用程序中,我通过回调/委托接收到一个指向C++结构的指针。我不确定class是否可行,但是将C++指针强制转换为适当的C#结构可以正常工作,因此我使用C#结构来存储数据。
现在我想传递一个对该结构的引用以进行进一步处理。
  • 我不能使用class,因为它可能无法完全“映射”到C++结构。
  • 我不想复制结构以获得更好的延迟。
我该怎么做?

这个例子演示了结构体是按值传递而不是按引用传递的:

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);
        }

    }
}

输出为:

inner a = 2
outer a = 1

4
小心你对struct的推理:在C++中,struct与class完全相同(除了默认访问权限),而在C#中它们截然不同 - 值类型和引用类型。很有可能你在C#中最好使用类 - 至少要阅读并了解struct在C#中的行为。 - Alexei Levenkov
我的许多Interop类确实是类,而不是结构体。如果您正确设置了封送处理(通常默认的封送处理会起作用),那么您可以使用类。如果有疑问,我首先尝试使用类。 - Matthew Watson
可变结构体是有害的。 - John Alexiou
2个回答

59

看起来你只想使用 ref 通过引用传递结构体:

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

并且在调用现场:

Foo(ref s1);

关于 C# 中的参数传递,请参阅我的文章,以了解更多详细信息。

请注意,除了互操作之外,我通常强烈建议不要使用可变结构体。尽管如此,在这里我可以理解其好处。


9
附注:处理结构体的代码必须非常小心,因为几乎所有与结构体相关的操作都会创建副本(除了数组,在其中你仍然可以使用元素的引用)。 - Alexei Levenkov
2
为什么您强烈建议不要通过“ref”传递结构体?据我所知,这与在C ++中通过引用传递相同(例如std :: string&),并且它可以消除可能大的结构体的不必要复制。 - Colin Basnett
1
@cmbasnett:我强烈建议不要使用可变结构体。通过引用传递结构体是可以的,但是a)一般情况下我建议避免使用大结构体;b)我会反对可变结构体(正如微软设计指南所建议的那样),因为它们可能会导致许多意外情况。 - Jon Skeet

0

您可以使用C# 7.2中的in关键字,如下所示:

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;
}

这确保了结构体参数 v 是:

  • 只读
  • 通过引用传递

IL 详细信息

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));

生成以下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)

你可以看到,使用in,我们将ldloca替换为ldloc

简而言之,结构体像ref一样传递,但由于in的保护写入受到编译器的保护。


1
当我写这篇文章时,我忘了插入参考;你可以通过在csproj中更改语言版本来尝试自己,例如通过设置<LangVersion>6</LangVersion>将为相同的上述代码产生以下错误:error CS8059: Feature 'readonly references' is not available in C# 6. Please use language version 7.2 or greater. - Lorenzo Delana

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接