¿Cómo pasar una estructura C# por referencia?

4 minutos de lectura

Avatar de usuario de Oleg Vazhnev
Oleg Vazhnev

En mi aplicación de C#, recibo un puntero a una estructura de C++ en devolución de llamada/delegado. no estoy seguro si class puede hacer el truco, pero solo lanzar un puntero de C++ a una estructura de C# adecuada funciona bien, así que estoy usando una estructura de C# para almacenar datos.

Ahora quiero pasar una referencia a la estructura para su posterior procesamiento.

  • no puedo usar class porque probablemente no se “asignará” perfectamente a la estructura de C++.
  • No quiero copiar la estructura para una mejor latencia.

¿Cómo puedo hacer eso?


Este ejemplo demuestra que struct se pasa por valor, no por referencia:

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

    }
}

La salida es:

inner a = 2
outer a = 1

  • Tenga cuidado con su razonamiento sobre la estructura: struct en C++ es exactamente igual que la clase (salvo la accesibilidad predeterminada), mientras que en C# son completamente diferentes: tipos de valor y referencia. Lo más probable es que realmente sea mejor con las clases en C#; al menos, lea y comprenda cómo struct comportarse en C#.

    – Alexéi Levenkov

    17 mayo 2013 a las 17:27

  • Una gran cantidad de mis clases de interoperabilidad son de hecho clases y no estructuras. Si configura la clasificación correctamente (y, a menudo, la clasificación predeterminada funciona), entonces puede usar una clase. En caso de duda, primero pruebo con una clase.

    –Matthew Watson

    17 mayo 2013 a las 17:47


  • Las estructuras mutables son malas.

    – John Alexiou

    8 de marzo a las 0:17

Parece que solo quieres usar ref para pasar la estructura por referencia:

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

Y en el sitio de la llamada:

Foo(ref s1);

Mira mi artículo sobre el paso de parámetros en C# para más detalles.

Tenga en cuenta que, aparte de la interoperabilidad, normalmente recomendaría encarecidamente contra usando estructuras mutables como esta. Sin embargo, puedo entender los beneficios aquí.

  • +1. Nota al margen: manejo de código struct debe escribirse con mucho cuidado ya que casi todas las operaciones con struct crear una copia (excepto matriz, donde aún puede tomar una ref de un elemento).

    – Alexéi Levenkov

    17 mayo 2013 a las 17:25


  • ¿Por qué recomienda encarecidamente no pasar estructuras por ref? Por lo que puedo decir, es lo mismo que pasar por referencia en C++ (p. std::string&), y puede eliminar la copia innecesaria de estructuras potencialmente grandes.

    – Colin Basnett

    21 de junio de 2015 a las 21:05

  • @cmbasnett: Recomiendo enfáticamente no usar estructuras mutables. Pasar una estructura por ref está bien, pero a) desaconsejaría estructuras grandes en general; b) Yo argumentaría en contra mudable structs (al igual que las pautas de diseño de Microsoft) ya que pueden causar una serie de sorpresas.

    – Jon Skeet

    21 de junio de 2015 a las 21:20

Avatar de usuario de Lorenzo Delana
lorenzo delana

Puedes usar c# 7.2 in palabra clave de la siguiente manera:

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

Esto asegura que el argumento de la estructura v sea:

  • solo lectura
  • pasado por árbitro

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

producir seguir 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)

puedes ver que usando in tenemos ldloca en lugar de ldloc.

En resumen, la estructura se pasa como si fuera un ref pero está protegido contra escrituras por el compilador gracias a la in.

  • Cuando escribo el post me faltó insertar este referencia ; puede probarlo usted mismo simplemente cambiando la versión de idioma en csproj, por ejemplo, configurando <LangVersion>6</LangVersion> producirá el siguiente error para el mismo código anterior error CS8059: Feature 'readonly references' is not available in C# 6. Please use language version 7.2 or greater.

    – Lorenzo Delana

    8 de marzo a las 8:18

¿Ha sido útil esta solución?