Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Validation in C# Records – How to prevent illegal state with the "with" keyword?

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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading