Properties can be initialized with the =
operator after the closing }
. The Coordinate
class below shows the available options for initializing a property:
public class Coordinate
{
public int X { get; set; } = 34; // get or set auto-property with initializer
public int Y { get; } = 89; // read-only auto-property with initializer
}
You can initialize auto-properties that have different visibility on their accessors. Here’s an example with a protected setter:
public string Name { get; protected set; } = "Cheeze";
The accessor can also be internal
, internal protected
, or private
.
In addition to flexibility with visibility, you can also initialize read-only auto-properties. Here’s an example:
public List<string> Ingredients { get; } =
new List<string> { "dough", "sauce", "cheese" };
This example also shows how to initialize a property with a complex type. Also, auto-properties can’t be write-only, so that also precludes write-only initialization.
Before C# 6, this required much more verbose code. We were using one extra variable called backing property for the property to give default value or to initialize the public property like below,
public class Coordinate
{
private int _x = 34;
public int X { get { return _x; } set { _x = value; } }
private readonly int _y = 89;
public int Y { get { return _y; } }
private readonly int _z;
public int Z { get { return _z; } }
public Coordinate()
{
_z = 42;
}
}
Note: Before C# 6.0, you could still initialize read and write auto implemented properties (properties with a getter and a setter) from within the constructor, but you could not initialize the property inline with its declaration
Initializers must evaluate to static expressions, just like field initializers. If you need to reference non-static members, you can either initialize properties in constructors like before, or use expression-bodied properties. Non-static expressions, like the one below (commented out), will generate a compiler error:
// public decimal X { get; set; } = InitMe(); // generates compiler error
decimal InitMe() { return 4m; }
But static methods can be used to initialize auto-properties:
public class Rectangle
{
public double Length { get; set; } = 1;
public double Width { get; set; } = 1;
public double Area { get; set; } = CalculateArea(1, 1);
public static double CalculateArea(double length, double width)
{
return length * width;
}
}
This method can also be applied to properties with different level of accessors:
public short Type { get; private set; } = 15;
The auto-property initializer allows assignment of properties directly within their declaration. For read-only properties, it takes care of all the requirements required to ensure the property is immutable. Consider, for example, the FingerPrint
class in the following example:
public class FingerPrint
{
public DateTime TimeStamp { get; } = DateTime.UtcNow;
public string User { get; } =
System.Security.Principal.WindowsPrincipal.Current.Identity.Name;
public string Process { get; } =
System.Diagnostics.Process.GetCurrentProcess().ProcessName;
}
Take care to not confuse auto-property or field initializers with similar-looking expression-body methods which make use of =>
as opposed to =
, and fields which do not include { get; }
.
For example, each of the following declarations are different.
public class UserGroupDto
{
// Read-only auto-property with initializer:
public ICollection<UserDto> Users1 { get; } = new HashSet<UserDto>();
// Read-write field with initializer:
public ICollection<UserDto> Users2 = new HashSet<UserDto>();
// Read-only auto-property with expression body:
public ICollection<UserDto> Users3 => new HashSet<UserDto>();
}
Missing { get; }
in the property declaration results in a public field. Both read-only auto-property Users1
and read-write field Users2
are initialized only once, but a public field allows changing collection instance from outside the class, which is usually undesirable. Changing a read-only auto-property with expression body to read-only property with initializer requires not only removing >
from =>
, but adding { get; }
.
The different symbol (=>
instead of =
) in Users3
results in each access to the property returning a new instance of the HashSet<UserDto>
which, while valid C# (from the compiler's point of view) is unlikely to be the desired behavior when used for a collection member.
The above code is equivalent to:
public class UserGroupDto
{
// This is a property returning the same instance
// which was created when the UserGroupDto was instantiated.
private ICollection<UserDto> _users1 = new HashSet<UserDto>();
public ICollection<UserDto> Users1 { get { return _users1; } }
// This is a field returning the same instance
// which was created when the UserGroupDto was instantiated.
public virtual ICollection<UserDto> Users2 = new HashSet<UserDto>();
// This is a property which returns a new HashSet<UserDto> as
// an ICollection<UserDto> on each call to it.
public ICollection<UserDto> Users3 { get { return new HashSet<UserDto>(); } }
}