covariant object initializers?
Asked Answered
S

3

6

say that I have an class that has a property that is a dictionary<string,bool>, using a object initializer I can use this syntax (which I think looks pretty clean):

new MyClass()
{
  Table = { {"test",true},{"test",false} }
}

however, outside of the initializer I can't do this:

this.Table = { {"test",true},{"test",false} };

Why are initializers a special case? I'd hazard a guess that it has something to do with LINQ requirements, covariance or whatnot but it feels a little incongruent not being able to use that kind of initializer everywhere...

Schaab answered 23/1, 2011 at 12:52 Comment(1)
I think the fact that the compiler error is "expected expression" is a big clue. In the second example, the syntax does not indicate an expression as you would usually expect i.e. there is no new operator. I suspect that the first example works as it is a special case and the compiler is more relaxed about what constitutes an expression syntactically. The benefit of the relaxed rules is terser syntax which is highly desirable for the context of object initializer syntax, it would look fugly otherwise.Bliss
C
11

The question is somewhat confusing, as the question has nothing to do with LINQ, nothing to do with generic variance, and features a collection initializer, as well as an object initializer. The real question is, as far as I can tell "why is it not legal to use a collection initializer outside of an object creation expression?"

The relevant design principle here is that in general, we want operations that create and initialize objects to have the word "new" in them somewhere as a signal to the reader that there is an object creation happening here. (Yes, there are a few exceptions to this rule in C#. As an exercise to the reader, see if you can name them all.)

Doing things your way makes it harder to reason about the code. Quick, what does this do?

d = new List<int>() { 10, 20, 30 };
d = { 40, 50, 60 };

Does the second line append 40, 50, 60 to the existing list? Or does it replace the old list with a new one? There's no "new" in there, so does the reader have an expectation that a new object has been created?

When you say

q = new Whatever() { MyList = { 40, 50, 60 } };

that doesn't create a new list; it appends 40, 50, 60 to an existing list allocated by the constructor. Your proposed syntax is therefore ambiguous and confusing as to whether a new list is created or not.

The proposed feature is both confusing and unnecessary, so it's unlikely to be implemented any time soon.

Conglutinate answered 23/1, 2011 at 16:38 Comment(11)
You're right, I initially thought it actually created a new collection, not just set it's values. So it's certainly confusing. Was just a bit curious about the different behavior, thanks for clearing it upSchaab
-1 Why do we can initialize arrays such way: int[] array = { 1, 2, 4 }; I think C# developer's was not thinking about 'new' keyword sending signals to reader. So, you can't say WHY they really did it.Quarta
@lazyberezovsky: That's one of the special cases that I mentioned. Note that it only works in the initializer of a declaration. As for the question of why the C# design committee really did or did not do something, I suspect that I have somewhat more insight into the matter than you do, as I have their design notes and I have discussed language design with at least two of the original designers for six to eight hours a week for the last five years.Conglutinate
If I was Eric, I would have said this to lazyberezovsky: 'Do you know who I am?' ;)Wasting
@Eric: Can I try to solve the exercise to the reader, at least partially? One of the cases is creating a new instance of a value type (does it count?), another case is boxing...Arda
You are not answering yous question why is it not legal to use a collection initializer outside of an object creation expression. You are talking about operations that create AND initialize objects. In the question above we have only INITIALIZATION of dictionary (if it is not created in ctor, you'll get an exception). So question why we can't use only initialization in other place (if collection already created). E.g. 'table = new Dictionary<string, bool>(); table = { {"test",true},{"test",false} };` PS it's not matter WHO u r - we just finding an answers here.Quarta
@Lazyberezovsky: First, yes, I did answer that question. The answer is "because it would be confusing to the reader of the code whether the intention is mutation of an existing object or a new object". Second, when the question is "what was the language design team thinking?" then I think it does matter whether the person answering the question is on the language design team or not. Do you disagree? Perhaps you can explain why you think that the C# designers do not consider the "new" keyword to be a signal to the reader. What evidence is your opinion based on?Conglutinate
@Ilya: Yep, those are the ones I was thinking of.Conglutinate
You didn't answer again :) I understand, that 'new' is a signal that object is created. So, if we do not have 'new' - object is not created. I think nothing is confusing here. Like in question example - in MyClass initializer there is no 'new' keyword and Table is just populated with values. So why we cannot use only initializer for populating table (without creation)?Quarta
@lazyberezovsky: You are suggesting that when the reader reads "d = {40, 50, 60}" the first thing that springs to their mind is "the collection that it refers to is being appended to"? That's not the first thing that springs to my mind. When I see "d = something" then I think that the contents of variable d are being replaced. I think that most people would agree.Conglutinate
@lazyberezovsky I think if you saw "d += {40, 50, 60}" you'd have a case for appending to a collection, but not "d = {40, 50, 60}". @Eric Lippert: I know that features start with -1million in cost ;) but can you guys consider the syntax "d += {40, 50, 60}" for collections?Moe
A
2

This limitation is far older than LINQ. Even back in C you could write

int numbers[5] = {1, 2, 3, 4, 5};

but you could not use this syntax to assign values to the array.

My guess about the reason behind this in C# is that usually you shouldn't use the same reference for two different objects. If you need to assign a new collection to an existing reference, most probably you didn't design your code very well, and you can either initialize the collection at definition, or use two separate references instead of one.

Arda answered 23/1, 2011 at 13:0 Comment(10)
This has got nothing to do with Linq as far as I can see. And how can you use the same reference for two different objects?Bliss
@chibacity: This is what I said. mko assumed this has something to do with LINQ, and I believe it doesn't. And you can use the same reference for two objects if you assign one collection to it and then assign another instead, like this: col = new Dictionary<string,bool>(...). I think this makes the code look worse and it's better to you use a new reference instead.Arda
@Ilya I see. It's just confusing bringing the C angle in. C# also has array initializers, but the above is not an example of an array initializer, it's a collection initializer. Quite different.Bliss
@chibacity Of course collections in C# and arrays in C are not the same thing, but the syntax is quite similar. Compare in C#: int[] n1 = new int[4] {2, 4, 6, 8}; I'm sorry, the syntax in my answer was wrong, I edited it now.Arda
@Ilya I don't doubt that the syntax is reminiscent, but that can't be the basis for an explanation - it's just text and looks similar.Bliss
Why -1? What's so bad about this answer?... I really want to learn if somebody thinks I made a mistake.Arda
IMO your explanation makes a lot of sense. My guess would be that too.Alleneallentown
@chibacity: Well then, forget about C and look back at C# 1.0 or C# 2.0, before LINQ was introduced. Even then this limitation was valid, my point being that LINQ has nothing to do with it.Arda
@chibacity I never said it was you :)Arda
@Ilya Sure, just eliminating myself from suspicion :)Bliss
L
0

Considering your syntax throws a NullReferenceException at runtime - are you sure you can use it?

public class Test
{
  public Dictionary<string, bool> Table {get; set;}
}

public void TestMethod()
{
  Test t = new Test { Table = { {"test", false} } }; //NullReferenceException
}

This compiles to the following (via reflector):

Test <>g__initLocal3 = new Test();
<>g__initLocal3.Table.Add("test", 0.0M);

As you can see, Table isn't initialized, thus producing a NullReferenceException at runtime.

If you create the Dictionary in the ctor of Test, the class initializer produces a cascade of Add statements, which is syntactic sugar in the initializer (for IEnumerables).

This probably wasn't introduced for normal code due to unforseen side effects we can't see or imagine. Eric Lippert might be able to help out as he probably has more insight on the matter at hand.

Lattermost answered 23/1, 2011 at 12:56 Comment(4)
yeah I know, but not the exact same syntaxSchaab
but isn't he asking why you have to provide the new Dictionary<string, bool> in front of it.Subaqueous
If you create the dictionary in the Test constructor or elsewhere, it won't throw the exception.Candidacandidacy
You get an exception if the collection isn't created in the constructor or directly in the field that's trueSchaab

© 2022 - 2024 — McMap. All rights reserved.