1

So I wanted to be able to mimic the "with" functionality of VB in C#, and came across a rather clever solution through StackOverflow:

public static x with<x>(this x item, Func<x,x> f)
{
    item = f(item);
    return item;
}

to implement you would do something like:

myObject = myObject.with(myVariable => {
    //do things
});

However, I ran into a snag when I tried to implement this inside a struct with one of the struct's fields. It said that "anonymous methods [...] inside structs cannot access members of 'this' [...]."

I did some research on this and found an answer to this question, which states ultimately that "this" for value types cannot be boxed. After researching what boxing means for C#, it makes sense, considering that the parameter for the function doesn't have a defined type.

My question is, why can't "this" for value types be boxed?

Tara
  • 389
  • 3
  • 14
  • 1
    Try check with https://stackoverflow.com/questions/15619407/why-do-i-have-to-copy-this-when-using-linq-in-a-struct-and-is-it-ok-if-i-do?rq=1. – Yarl Mar 12 '19 at 17:25
  • 5
    the *dereferenced value* of `this` **absolutely** can be *boxed*: `object obj = this;` - done. It can't be *implicitly captured*, though; that's not quite the same thing. – Marc Gravell Mar 12 '19 at 17:26
  • the compiler suggests, for CS1673: "Consider copying 'this' to a local variable outside the anonymous method, lambda expression or query expression and using the local instead." - have you tried that? note that `this` is not a `MyStruct`, it is a `ref MyStruct`. That's *probably* a lot of the reasoning here. I tried a `Foo copy = this;` then used `copy.Whatever()` in the anonymous method - worked fine. – Marc Gravell Mar 12 '19 at 17:27
  • 4
    It would also really help if you'd give a complete example of the code that's failing, and what you expected it to do. At the moment you're referring to code you haven't shown, so we can *guess* what we think that means, but we could well be guessing incorrectly. – Jon Skeet Mar 12 '19 at 17:28
  • @MarcGravell after doing some further research on closures, I *think* I have a better understanding of this, but some things still don't make sense. From what I understand, it has something to do with the lifetime of the dereferenced value type over the lifetime of the enclosure, but I'm not sure why that would matter. – Tara Mar 12 '19 at 21:43
  • @JonSkeet the call to the "with" extension method is within a struct that has a field I was trying to call from within the anonymous function I passed to with "with" method. I wasn't really concerned about the error itself as there are a number of ways around it. I was more concerned with the *why* of the error than a workaround for the error. – Tara Mar 12 '19 at 23:25
  • 1
    But even the "why" would be much easier to explain with a very concrete example. In general, when it's possible to provide a [mcve] - which I believe it would be in this case - it massively improves a question. – Jon Skeet Mar 13 '19 at 06:29
  • @JonSkeet Thanks for that link, it was helpful in understanding your point. So by complete, that would also require the class declarations in laguages like C# where methods are required to live in classes (especailly for extension methods which are required to live in static classes), correct? (What about using statements?) – Tara Mar 14 '19 at 00:54
  • @TaraStahler: Yes, ideally there should be everything necessary so someone can just "copy, paste, compile" - seeing the errors you've listed if it doesn't compile, or for code that *does* compile, it should be ready to run, showing the same output you've listed in the question. That's certainly my preference. But "minimal" is also important - the code that you present may end up being quite different to your real code, because it would be the bare minimum required to demonstrate the problem. It needs to be the same in the important aspects of course - and that can be tricky. – Jon Skeet Mar 14 '19 at 07:56

2 Answers2

2

The main point here is that for a method on a struct, the meaning of this is not a value (i.e. an value of your SomeStruct), but rather a reference (a ref SomeStruct, or effectively an in SomeStruct in the case of a readonly struct).

You can't box a managed pointer of this form - that isn't a scenario supported by the runtime. Managed pointers are only meant to be on the stack. In fact, currently you can't even have a ref SomeStruct field in a custom ref struct that can't escape the stack.

The compiler could cheat by pretending to do that - i.e. by dereferencing the managed pointer from a ref SomeStruct into a SomeStruct and creating a capture context where this is interpreted as "the SomeStruct that we dereferenced earlier", but ... then the compiler can't guarantee the same behaviours and outcomes (actually, I suspect a case could be made for this in the readonly struct scenario, but ... it is probably easier not to introduce that subtle distinction).

Instead, the compiler suggests that you effectively do the above step manually; since the compiler is no longer dealing in terms of this, it no longer has to pretend to respect the usual outcomes for dealing with this, and instead only has to guarantee the behaviours of an explicitly dereferenced copy of the value. That's why it advises (at least on current compiler versions):

Consider copying 'this' to a local variable outside the anonymous method, lambda expression or query expression and using the local instead.

Most lambdas, local methods, etc can therefore be achieved by the pragmatic step of:

MyStruct copy = this; // dereference

then in your lambda / local method / etc: instead of touching Something aka this.Something - touch copy.Something. It is now only copy that is being included in the capture-context, and copy is not bound by the ref SomeStruct rules (because: it isn't a ref SomeStruct - it is a SomeStruct).

It does, however, mean that if your intent was to mutate this (and have that visible in-place, rather than as a return value), then it won't work. You'll only be mutating the copy (i.e. copy). This is exactly what the compiler would have had to do anyway if it had lied, but at least now the copy step (dereference) is explicit and visible in your code.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • That makes much more sense now. Thanks! ^^ So if I understand correctly, basically, because "this" is a pointer and therefore has to remain on the stack, anything that would be implicitly created by the CLR wouldn't be the same object as what "this" is referencing, so to avoid confusion they force you to explicitly create the copy yourself before passing it. – Tara Mar 14 '19 at 00:38
  • 1
    @TaraStahler 95% right - the only problem is the word "object" in "wouldn't be the same object as what 'this' is referencing" - there isn't necessarily an *object* at all in this scenario; replace that with "thing" and you're on the money :) the distinction here is actually really subtle, and would only impact value-types that are inherently risky in the first place, but ... the language folks are really really fussy about that kind of thing – Marc Gravell Mar 14 '19 at 10:08
1

I presume you know about setting properties during construction, which is similar (and more idiomatic of c#) ?

class MySpecialClass
{
   public string Property1 {get;set;}
   public int Length {get;set;}
   public float Width {get;set;}
}

var p = new MySpecialClass
{
  Property1 = "PropertyValue",
  Length = 12,
  OtherThing = 1.234F
};
Neil
  • 11,059
  • 3
  • 31
  • 56