I have a C# record like so:
public record CodeWithContext
{
public string Description { get; init; }
public string Value { get; init; }
public string Code { get; init; }
public CodeWithContext(string desc, string val, string code)
{
if (string.IsNullOrEmpty(desc) &&
string.IsNullOrEmpty(val) &&
string.IsNullOrEmpty(code))
{
throw new ArgumentException("text");
}
Description = desc;
Value = val;
Code = code;
}
}
An ArgumentException is thrown when all properties are set to an empty string.
However, I’ve noticed that when I create a new object using the with keyword, it bypasses the constructor and I can create an object in what should be an illegal state:
var code = new CodeWithContext("description", "value", "code");
var cut = code with { Code = "", Value = "", Description = "" };
In this case, cut is a CodeWithContext in an illegal state and no error is thrown.
I would like the validation logic to apply regardless of whether the object is created using the constructor or copied using the with keyword. How can I achieve this?
>Solution :
The behavior you’re observing is due to how records work in C#. When you use the with keyword, it creates a copy of the record with the specified properties modified. This copy is created by calling a compiler-generated method, not by invoking the constructor you defined. Since your validation logic is in the constructor, it doesn’t get called when you create a copy with the with expression.
This can be achieved using a custom property with a backing field and validation in the setter:
public record CodeWithContext
{
private string _description;
private string _value;
private string _code;
public string Description
{
get => _description;
init
{
_description = value;
Validate();
}
}
public string Value
{
get => _value;
init
{
_value = value;
Validate();
}
}
public string Code
{
get => _code;
init
{
_code = value;
Validate();
}
}
public CodeWithContext(string desc, string val, string code)
{
_description = desc;
_value = val;
_code = code;
Validate();
}
private void Validate()
{
if (string.IsNullOrEmpty(_description) && string.IsNullOrEmpty(_value) && string.IsNullOrEmpty(_code))
{
throw new ArgumentException("Invalid arguments for creating CodeWithContext");
}
}
}
In this example, the Validate method is called whenever any of the properties are set (including during object initialization). This ensures that the validation logic is applied both when creating a new object and when creating a copy with the with keyword.