In C#, what is the difference between these two statements
var x = new Thing{ A = 10, B = 20 }; var x = new Thing(A:10, B:20);
(Notice the difference in styles of bracket, and the symbol between e.g. A and 10.)
The answer is:
- The first is using a constructor that takes no parameters, and then is setting the object’s properties called A and B;
- The second is using a constructor that takes at least two parameters, one called A and the other called B. The parameters are referred to by name, rather than by position. What properties the object has is unknown, and what effect this constructor has on any properties is also unknown.
To explore this a little further, here is a class that can be initialised in a variety of ways. It uses default values in a number of different places:
- A parameter to a constructor
- An auto-property
- A field that backs a non-auto-property.
public class Point { private double _X; public double X { get { return _X; } set { Console.WriteLine($"In X's setter, setting X to {value}"); _X = value; } } private double _Y; public double Y { get { return _Y; } set { Console.WriteLine($"In Y's setter, setting Y to {value}"); _Y = value; } } private double _Z = 0; public double Z { get { return _Z; } set { Console.WriteLine($"In Z's setter, setting Z to {value}"); _Z = value; } } public double T { get; set; } = 273; public Point() { Console.WriteLine($"In parameter-less constructor"); } public Point(double angle, double radius) { Console.WriteLine($"In polar constructor angle={angle}, radius={radius}"); X = radius * Math.Cos(angle); Y = radius * Math.Sin(angle); // deliberately miss out Z } public Point(int x, int y, int z, int t) { Console.WriteLine($"In int constructor x={x}, y={y}, z={z}, t={t}"); X = x; Y = y; Z = z; T = t; } public Point(string x, string y, string z = "-4") { Console.WriteLine($"In string constructor x={x}, y={y}, z={z}"); double d; Double.TryParse(x, out d); X = d; Double.TryParse(y, out d); Y = d; Double.TryParse(z, out d); Z = d; } public override string ToString() { return $"({X}, {Y}, {Z}, {T})"; } }
It has 4 properties: X, Y, Z and T. X-Z have non-default setters so that they can be logged, which means that they need an explicit field to back them up. T is an auto-property, so its field is auto-generated for me. The field behind Z is intialised to 0. T defaults to 273 at the property level.
There are four constructors, plus an override of ToString. The constructors are as follows:
- Takes no parameters.
- Takes two parameters – doubles for angle and radius. These are converted into x/y co-ordinates, and then the setters for X and Y are used to store the calculated co-ordinates.
- Takes four ints, which are directly stored in the corresponding properties using the setters.
- Takes two or three strings – x and y are mandatory and z is optional. If z is missing its value defaults to -4. Assuming that the strings parse correctly to doubles, the x-z values are stored in the corresponding properties using the setters.
The following code uses the various constructors to create a few instances of Point.
Console.WriteLine("Creating p1 using new Point{X = 10, Y = 20}"); Point p1 = new Point{X = 10, Y = 20}; Console.WriteLine($"p1 = {p1.ToString()}"); Console.WriteLine(""); Console.WriteLine("Creating p2 using new Point(angle: Math.PI / 2, radius: 40)"); Point p2 = new Point(angle: Math.PI / 2, radius: 40); Console.WriteLine($"p2 = {p2.ToString()}"); Console.WriteLine(""); Console.WriteLine("Creating p3 using new Point(angle: Math.PI, radius: 30) { Z = 12 }"); Point p3 = new Point(angle: Math.PI, radius: 30) { Z = 12 }; Console.WriteLine($"p3 = {p3.ToString()}"); Console.WriteLine(""); Console.WriteLine("Creating p4 using new Point(\"5\", \"6\")"); Point p4 = new Point("5", "6"); Console.WriteLine($"p4 = {p4.ToString()}"); Console.WriteLine(""); Console.WriteLine("Creating p5 using new Point(x: \"0\", y: \"12\", z: \"15\")"); Point p5 = new Point(x: "0", y: "12", z: "15"); Console.WriteLine($"p5 = {p5.ToString()}"); Console.WriteLine(""); Console.WriteLine("Creating p6 using new Point(100, 101, 102, 44)"); Point p6 = new Point(100, 101, 102, 44); Console.WriteLine($"p6 = {p6.ToString()}"); Console.WriteLine("");
The output of this code is as follows
Creating p1 using new Point{X = 10, Y = 20}
In default constructor
In X’s setter, setting X to 10
In Y’s setter, setting Y to 20
p1 = (10, 20, 0, 273)Creating p2 using new Point(angle: Math.PI / 2, radius: 40)
In polar constructor angle=1.5707963267949, radius=40
In X’s setter, setting X to 2.44921270764475E-15
In Y’s setter, setting Y to 40
p2 = (2.44921270764475E-15, 40, 0, 273)Creating p3 using new Point(angle: Math.PI, radius: 30) { Z = 12 }
In polar constructor angle=3.14159265358979, radius=30
In X’s setter, setting X to -30
In Y’s setter, setting Y to 3.67381906146713E-15
In Z’s setter, setting Z to 12
p3 = (-30, 3.67381906146713E-15, 12, 273)Creating p4 using new Point(“5”, “6”)
In string constructor x=5, y=6, z=-4
In X’s setter, setting X to 5
In Y’s setter, setting Y to 6
In Z’s setter, setting Z to -4
p4 = (5, 6, -4, 273)Creating p5 using new Point(x: “0”, y: “12”, z: “15”)
In string constructor x=0, y=12, z=15
In X’s setter, setting X to 0
In Y’s setter, setting Y to 12
In Z’s setter, setting Z to 15
p5 = (0, 12, 15, 273)Creating p6 using new Point(100, 101, 102, 44)
In int constructor x=100, y=101, z=102, t=44
In X’s setter, setting X to 100
In Y’s setter, setting Y to 101
In Z’s setter, setting Z to 102
p6 = (100, 101, 102, 44)
Note that T is an auto-property, and so using its setter is never logged.
p1 is created using the no-parameter constructor, and then the properties X and Y are set. new Point{X=10, Y=20} is short-hand for new Point(){X=10, Y=20}. The field behind Z stays at its initial value of 0, and the T property defaults to 273.
p2 is created using the polar constructor. The same result would have happened if the call had been new Point(radius:40, angle: Math.PI / 2) – the order doesn’t matter as the parameter names are specified. The polar constructor uses the setters for X and Y to store the values it calculates. As in p1, the field behind Z stays at its initial value of 0, and the T property defaults to 273.
p3 is also created using the polar constructor, but after the object is constructed its Z property is set to 12. The T property defaults to 273.
p4 is created using the string constructor, passing in the minimum number of values. This means that the z parameter defaults to -4. The X, Y and Z setters are used to set the properties according to the values available in the constructor (the values passed in and the default). The T property defaults to 273.
p5 is also created using the string constructor, passing in all possible values. z therefore has a non-default value of 15, and then things progress as with p4.
p6 is created using the 4 ints constructor, assigning values to parameters by position rather than by name (unlike the calls we happen to have made to the polar constructor). The setters for X-Z and T are all used (but T’s setter doesn’t log anything).