3

In the following test code I do not understand why the first line of TestMethod is legal, but the remaining two lines are not:

public class Bar
{
    public string Prop { get; set; }
}

public class Foo
{
    public int Primitive { get; } = 0;
    public Func<int, int> Function { get; } = (i) => i;
    public Bar Bar { get; } = new Bar();
}

public class TestClass
{
    public void TestMethod()
    {
        var baz = new Foo { Bar = { Prop = "Hello World!" } }; // legal
        var buzz = new Foo { Primitive = 1 }; // Property or indexer 'Foo.Primitive' cannot be assigned to -- it is read only
        var fuzz = new Foo { Function = (i) => 2 }; // Property or indexer 'Foo.Function' cannot be assigned to -- it is read only
    }
}

If it is legal to assign to class-type read only properties like Bar in an object initializer (which it is; and which makes sense, since 'read only' really means 'read only except at class construction time' in C# as I understand it) then why is it illegal to assign to properties with types like like int and Func<int, int>?

This seems even more confusing since (again, as I understand it) Func<int, int> is a reference type, like the Bar property but unlike the int property.

MikeBeaton
  • 3,314
  • 4
  • 36
  • 45
  • 1
    What's confusing is the syntax implying that `Bar = { ... }` is itself an assignment -- it's not. The object initializer is a shorthand for setting the properties of the object in question, *not* the property itself. It's not a question of primitive vs. non-primitive; `new Foo { Bar = null }` is equally illegal. – Jeroen Mostert May 01 '19 at 11:10
  • Thank you @JeroenMostert! Yes, the syntax very much does imply that, and is indeed where I got confused! I wonder (as I asked to @JonSkeet below as well) is there anywhere else at all in C# where `a = ...` does *not* assign to `a`?!? – MikeBeaton May 01 '19 at 11:28
  • 1
    A quick check of the grammar allows me to tentatively say "no". In a LINQ query, technically, `let [id] = [value]` is not an assignment, but it does ultimately translate to one, so that doesn't really count. There are a few more non-assignment uses of `=` (like `using [alias] = [namespace].[type]`) but those occur in a clearly different context. If you really wanted to know for absolute sure you could make it a follow-up question. :-) – Jeroen Mostert May 01 '19 at 11:36
  • Oh, here's another one: an optional parameter declaration (`void foo(int a = 0)`) does not actually assign anything, despite appearances; instead a call like `foo()` will be expanded (at the caller site) to `foo(0)`. Arguably, that's another instance where blurring the semantics is actually helpful, as most people would not be confused when thinking of it as an assignment. (But it's still good to know that it's not, so you can explain what happens if changes are made to the signature of `foo`.) – Jeroen Mostert May 01 '19 at 11:55
  • @JeroenMostert - Many thanks! You did answer first I think, but since it's a comment not an answer I can't chose to accept this one... ; – MikeBeaton May 01 '19 at 11:56
  • Being the first to say something helpful is not the same as posting a complete, helpful answer, so it's right and proper for others to get the credit. :-) – Jeroen Mostert May 01 '19 at 11:57
  • Thanks for the default parameter pointer, though in that syntax (which I've used a lot and never did find confusing, though I take your technical point!) it is still true that if `a` was a reference type then by the end of things `a`'s reference would have been set, at least! (i.e. unlike the `Bar = { ... }` syntax which I stumbled upon here!) – MikeBeaton May 01 '19 at 12:02
  • Your initial comment actually would have been enough to be a complete and helpful answer to this reader (and questioner) but I understand the point - thanks again! – MikeBeaton May 01 '19 at 12:05

2 Answers2

4
var baz = new Foo { Bar = { Prop = "Hello World!" } }; // legal

This is not an assignment to Bar. It is essentially:

var tmp = new Foo();
tmp.Bar.Prop = "Hello World!";
var baz = tmp;

At no point is .Bar assigned to.

Conversely, however:

var buzz = new Foo { Primitive = 1 };

is:

var tmp = new Foo();
tmp.Primitive = 1;
var buzz = tmp;

which does assign to .Primitive.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Thanks to yourself, @JonSkeet and @JeroenMostert in comments for very prompt and helpful answers. I must say, it feels that the `Bar = { Prop = "Hello World!" }` syntax is *very* confusing, since `Bar = ...` simply isn't an assignment to `Bar` in this case! Or am I still very confused?! – MikeBeaton May 01 '19 at 11:31
  • 1
    @MikeBeaton the important distinction is `Bar = { ... }` vs `Bar = new TheType { ... }` - the second is an assignment to `Bar`. Yes, I agree this is confusing, but a: I can't think of a better way of expressing it, and b: even if I could, that ship has long sailed – Marc Gravell May 01 '19 at 11:33
  • `Bar.Prop = "Hello World!"` seems like a more obviously intuitive syntax for what is happening; but then I gather that I could have put multiple properties. So maybe `Bar { Prop = "Hello World!", Prop2 = "Goodbye cruel world, I'm leaving you today, Goodbye, Goodbye, Goodbye" }` with no initial `=`? But yes, I realise the ship has long sailed. – MikeBeaton May 01 '19 at 11:52
3

If it is legal to assign to class-type read only properties like Bar in an object initializer (which it is [...])

No, it isn't. Object initializers call the constructor and then assign to properties. For example, this code:

var buzz = new Foo { Primitive = 1 };

is just syntactic sugar for this:

var buzz = new Foo();
buzz.Primitive = 1;

That's not valid if Primitive is a read-only property.

(To be very pedantic, it's more generally appropriate to regard it as assigning to a temporary local variable, setting the properties, and then assigning to buzz at the very end, but we'll ignore that for now.)

The code that you've observed working isn't setting those read-only properties - it's getting them, and then setting values using the returned reference. So this:

var baz = new Foo { Bar = { Prop = "Hello World!" } }

is actually equivalent to:

var baz = new Foo();
baz.Bar.Prop "Hello World!";

That's entirely valid, even though Bar is read-only.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thank you. I had more or less got to seeing already that this was what was going on, since posting a short while ago! But then what is up with the `Bar = { Prop = "Hello World!" }` syntax? Is there anywhere else in C# where `a = b` does *not* assign to `a` (which, again, if I'm not still wildly confused(!) is what's going on here)? – MikeBeaton May 01 '19 at 11:26
  • Both your's and @MarcGravell's answers are entirely correct, helpful and to the point - but I can't accept two! Many thanks for this and for everything that I see that you do here for the community on Stack Overflow! – MikeBeaton May 01 '19 at 12:04