The C# 9 init-only setters

In a previous post in which we discussed about the Null Object Pattern, we discussed of a hackish approach to make sure that the default properties cannot be changed.

Please note that the code snippets from this post have been run in LinqPad so they can be used as is, to run them in Visual Studio, you might be require to make small changes.

In this post we will discuss about the somewhat new keyword that was introduced in C# 9 with .NET 5, and that is the init keyword.

How does it work?

This keyword will allow us to create immutable objects (at least in regards to the properties that are marked with init) so that we can make sure no one can change the value later on.

So how do we use this keyword? Well this keyword is to be used instead of the set keyword when declaring properties like so:

class TestClass1
{
	public int TestClass1IntProperty { get; set; }
}

class TestClass2
{
	public int TestClass1IntProperty {get; init; }
}

And the usage of this properties can be seen as follows:

void Main()
{
	TestClass1 test1 = new TestClass1
	{
		TestClass1IntProperty = 42
	};
	
	test1.TestClass1IntProperty = 43; // this is ok because the property is muttable.
	
	TestClass2 test2 = new TestClass2
	{
		TestClass2IntProperty = 42
	};
	
	// this will show the following compilator error: 
	// CS8852 Init-only property or indexer '<full name of property>'
	//	can only be assigned to an object initializer,
	//	or on 'this' or 'base' in an instance constructor or an 'init' accessor.
	test2.TestClass2IntProperty = 43; 
}

Because this keyword is used instead of the set keyword for properties, this means we could also have classes declared with only some of the properties being marked with init for immutability purposes like so:

class TestClass3
{
	public int TestClass3IntProperty { get; set; }
	public int TestClass3IntProperty2 { get; init; }
}

Also, because we’re talking about properties, we are also talking about generated methods with backing fields, as such, we could run custom code when setting the value for a property but only when initializing the instance like so:

class TestClass4
{
	private int _testClass4IntProperty;
	public int TestClass4IntProperty
	{
		get => _testClass4IntProperty;
		init => _testClass4IntProperty = value;
	}

	private int _testClass4IntProperty2;
	public int TestClass4IntProperty2
	{
		get { return _testClass4IntProperty2; }
		init { _testClass4IntProperty2 = value; }
	}
}

So far so good, not too much to explore on this front, though the feature is very powerful for designing safer code, but let’s look at some other scenarios.

Things to keep in mind

Reference properties only keep a reference

Let’s say we have the following class:

class TestClass5
{
	public int TestClass5IntProperty { get; set; }
}

class TestClass6
{
	public TestClass5 TestClass6RefProperty { get; init; }
}

Just like if we were to declare a field as readonly or we have a property with only a getter, this only refers to the instance held inside that field/property, as such we can still change the non-immutable properties of instances that are referenced by an immutable field/property:

void Main()
{
	TestClass6 test = new TestClass6
	{
		TestClass6RefProperty = new TestClass5
		{
			TestClass5IntProperty = 42
		}
	};

	// this is valid code because the reference to the instance of TestClass6RefProperty has not changed.
	test.TestClass6RefProperty.TestClass5IntProperty = 445;

	// this will throw an error because we're trying to change the reference of the init property
	test.TestClass6RefProperty = new TestClass5 { TestClass5IntProperty = 13 };
}

Reflection can still bypass the restriction

Let us have a look at the following code and explain why it works, we will making use the the TestClass2 we defined before for brevity’s sake.

void Main()
{
	TestClass2 test = new TestClass2
	{
		TestClass2IntProperty = 42
	};
	
	Console.WriteLine(test.TestClass2IntProperty); // will output 42
	
	PropertyInfo setPropertyInfo = test.GetType().GetProperty(nameof(TestClass2.TestClass2IntProperty));
	setPropertyInfo.SetValue(test, 512);
	
	Console.WriteLine(test.TestClass2IntProperty); // will output 512
}

If run the code in the example above, we will that that we managed to change the value and have no compilation error, so just because the developer isn’t allowed to directly change the value of an immutable property, that doesn’t mean that they can not.

I believe that the reason this works is that many frameworks rely upon reflection to do their work. Frameworks like Entity Framework and other ORMs, serialization frameworks, and so on.

I hope you enjoyed this post and in the next one we will be having a look at records.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s