13
struct Foo {
    int i;
    public ref int I => ref i;
}

This code raises compile error CS8170, but if Foo is a class, it doesn't. Why can a structure not return a member as a reference?

Boann
  • 48,794
  • 16
  • 117
  • 146
20chan
  • 167
  • 1
  • 10
  • 2
    It is surprisingly difficult to find documentation on CS8170. Normally MSDN has *something*, even a mostly-empty placeholder page, but in this case... I find some Github issues, Roslyn unit tests / source code, and that's it. –  Mar 09 '18 at 07:18
  • 15
    Since value types (structs) are allocated on the stack, a reference returned for one of its members would become invalid as soon as the variable went out of scope, so this error makes sense for code safety. – Bradley Smith Mar 09 '18 at 07:22
  • 9
    @BradleySmith - please stop repeating this tired and not generally true statement. structs are *sometimes* allocated on the stack. There are *plenty* of times when they are not. – Damien_The_Unbeliever Mar 09 '18 at 07:26
  • @BradleySmith They are not *always* allocated on the stack though. – Ivan Stoev Mar 09 '18 at 07:27
  • 9
    Surely the very fact that they might be allocated on the stack is enough to explain the compiler error, though? – Bradley Smith Mar 09 '18 at 07:27
  • @BradleySmith he has a point. Your original comment says they *are* allocated on the stack, not that they *might be*. –  Mar 09 '18 at 07:31
  • Maybe if Microsoft could update their documentation we wouldn't see _"structs are allocated on the stack"_ statements. – FCin Mar 09 '18 at 07:32
  • 2
    MS describes it this way: *However, unlike classes, structs are value types and do not require heap allocation.* See: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/structs – Stefan Mar 09 '18 at 07:32
  • 1
    I wish i had some popcorn, however i think its a fair enough explanation that it would be invalid when it falls out of scope (in some circumstances) and hence the compiler error. Where is Eric Lippert lurking – TheGeneral Mar 09 '18 at 07:35
  • Funny thing is: it also aplies to `this`, but according to the specs, `this` would mean a copy of the struct, which makes the `lives on stack` argument (for `this`) invalid. See: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#this-access – Stefan Mar 09 '18 at 07:39
  • we should have the possibility to send this question to @EricLippert. I guess it would be a good address for this. – Mong Zhu Mar 09 '18 at 07:39
  • https://stackoverflow.com/questions/12801462/why-anonymous-methods-inside-structs-can-not-access-instance-members-of-this – Essigwurst Mar 09 '18 at 07:44
  • 2
    @MongZhu [summoning Eric Lippert does work](https://stackoverflow.com/questions/4892704/why-is-dynamic-not-covariant-and-contravariant-with-respect-to-all-types-when#comment5444158_4892704), although its easier to use the Contact link on his blog. –  Mar 09 '18 at 07:45
  • @Amy unbelievable! you actually did it! Let's try again. I am very much impressed :) – Mong Zhu Mar 09 '18 at 07:47
  • Which IDE/compiler are you using? because when I try to compile your code I have many compilation errors but none of them is named CS8170 – Oxald Mar 09 '18 at 08:04
  • @Oxald I'm using VS2017 and compiler version is 7.0 . Did you checked your compiler version >= 7.0? – 20chan Mar 09 '18 at 08:06
  • 6
    Truly grokking ref return limitations requires understanding pointers. Pointers are dangerous, a standard programming bug in a language that gives them unrestricted abilities is using a pointer that no longer addresses a valid memory location. C# is not such a language, the compiler enforces syntax that ensures such a bug can never creep in. An additional requirement is that it needs to make sure that the garbage collector can properly discover what the pointer is pointing to, the bigger problem in this snippet. https://en.wikipedia.org/wiki/Dangling_pointer – Hans Passant Mar 09 '18 at 09:31
  • 3
    There's some additional notes here: https://github.com/dotnet/csharplang/blob/master/meetings/2015/LDM-2015-09-01.md#this-in-structs – Pete Garafano Mar 09 '18 at 18:26

1 Answers1

1

I think I found a way around it:

class Program
{
    static void Main(string[] args)
    {
        Foo temp = new Foo(99);
        Console.WriteLine($"{Marshal.ReadInt32(temp.I)}");
        Console.ReadLine();
    }
}
struct Foo
{
    int i;
    public IntPtr I;

    public Foo(int newInt)
    {
        i = newInt;
        I = GetByRef(i);
    }
    static unsafe private IntPtr GetByRef(int myI)
    {
        TypedReference tr = __makeref(myI);
        int* temp = &myI;
        IntPtr ptr = (IntPtr)temp;
        return ptr;
    }
}

Not that its a good idea- too many dire warnings. However, I do believe it is achieving what you want by returning a reference to the struct member, which you can then marshal to get the original value.

daniel_sweetser
  • 381
  • 1
  • 8