Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does a combination of object and collection initializers use Add method?

The following combination of object and collection initializers does not give compilation error, but it is fundamentally wrong (https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers#examples), because the Add method will be used in the initialization:

public class Foo
{
    public List<string> Bar { get; set; }
}

static void Main()
{
    var foo = new Foo
    {
        Bar =
        {
            "one",
            "two"
        }
    };
}

So you'll get NullReferenceException. What is the reason for making such an unsafe decision while developing the syntax of the language? Why not to use initialization of a new collection for example?

like image 700
smolchanovsky Avatar asked Oct 20 '18 14:10

smolchanovsky


People also ask

What is the purpose of object initialization?

Object initializers are used to assign values to an object's properties or fields at creation time without invoking the constructor. If you create an object and then right after that assign values to its properties, ReSharper suggests using an object initializer.

What is collection initializers in C#?

Collection initializers let you specify one or more element initializers when you initialize a collection type that implements IEnumerable and has Add with the appropriate signature as an instance method or an extension method. The element initializers can be a simple value, an expression, or an object initializer.

What is an initializer list Visual Basic?

Collection initializers provide a shortened syntax that enables you to create a collection and populate it with an initial set of values.

What is object initialization in C++?

Dynamic initialization of object in C++ Dynamic initialization of object refers to initializing the objects at a run time i.e., the initial value of an object is provided during run time. It can be achieved by using constructors and by passing parameters to the constructors.


2 Answers

First, it's not only for combination of object and collection initializers. What you are referring here is called nested collection initializers, and the same rule (or issue by your opinion) applies to nested object initializers. So if you have the following classes:

public class Foo
{
    public Bar Bar { get; set; }
}

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

and you use the following code

var foo = new Foo
{
    Bar = { Baz = "one" }
};

you'll get the same NRE at runtime because no new Bar will be created, but attempt to set Baz property of the Foo.Bar.

In general the syntax for object/collection initializer is

target = source

where the source could be an expression, object initializer or collection initializer. Note that new List<Bar> { … } is not a collection initializer - it's an object creation expression (after all, everything is an object, including collection) combined with collection initializer. And here is the difference - the idea is not to omit the new, but give you a choice to either use creation expression + object/collection initializer or only initializers.

Unfortunately the C# documentation does not explain that concept, but C# specification does that in the Object Initializers section:

A member initializer that specifies an object initializer after the equals sign is a nested object initializer, i.e. an initialization of an embedded object. Instead of assigning a new value to the field or property, the assignments in the nested object initializer are treated as assignments to members of the field or property. Nested object initializers cannot be applied to properties with a value type, or to read-only fields with a value type.

and

A member initializer that specifies a collection initializer after the equals sign is an initialization of an embedded collection. Instead of assigning a new collection to the target field, property or indexer, the elements given in the initializer are added to the collection referenced by the target.


So why is that? First, because it clearly does exactly what you are telling it to do. If you need new, then use new, otherwise it works as assignment (or add for collections).

Other reasons are - the target property could not be settable (already mentioned in other answers). But also it could be non creatable type (e.g. interface, abstract class), and even when it is a concrete class, except it is a struct, how it will decide that it should use new List<Bar> (or new Bar in my example) instead of new MyBarList, if we have

class MyBarList : List<Bar> { }

or new MyBar if we have

class MyBar : Bar { }

As you can see, the compiler cannot make such assumptions, so IMO the language feature is designed to work in the quite clear and logical way. The only confusing part probably is the usage of the = operator for something else, but I guess that was a tradeoff decision - use the same operator = and add new after that if needed.

like image 81
Ivan Stoev Avatar answered Sep 21 '22 14:09

Ivan Stoev


Take a look at this code and the output of it due to the Debug.WriteLine():

public class Foo
{
    public ObservableCollection<string> _bar = new ObservableCollection<string>();

    public ObservableCollection<string> Bar
    {
        get
        {
            Debug.WriteLine("Bar property getter called");
            return _bar;
        }

        set
        {
            Debug.WriteLine("Bar allocated");
            _bar = value;
        }
    }

    public Foo()
    {
        _bar.CollectionChanged += _bar_CollectionChanged;
    }

    private void _bar_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        Debug.WriteLine("Item added");
    }
}

public MainWindow()
{
    Debug.WriteLine("Starting..");

    var foo = new Foo
    {
        Bar =
        {
            "one",
            "two"
        }
    };

    Debug.WriteLine("Ending..");
}

The output is:

Starting..
Bar property getter called
Item added
Bar property getter called
Item added
Ending..

For you questions: What is the reason for making such an unsafe decision while developing the syntax of the language? Why not to use initialization of a new collection for example?

Answer: As you can see the intention of the designer of that feature was not to reallocate the collection but rather to help you add items to it more easily considering that you manage your collection allocation by yourself.

Hope this clear things out ;)

like image 44
Eibi Avatar answered Sep 19 '22 14:09

Eibi