The pros and cons of using a structure to provide an inline type check

Update: Reinstalled on the program control unit due to the fact that it is postponed mainly on the basis of opinion.

Typically, domain objects have properties that can be represented by an inline type, but whose real values ​​are a subset of the values ​​that can be represented by this type.

In these cases, the value can be stored using the built-in type, but it is necessary that the values ​​are always checked at the input point, otherwise we could work with an invalid value.

One way to resolve this issue is to save the value as a custom struct that has one private readonly supporting field of built-in type and whose constructor checks the provided value. We can always be sure that we will use only verified values ​​using this type of struct .

We can also provide casting operators from and to the base built-in type so that values ​​can be easily entered and exited as the base type.

Let us take as an example a situation where we need to represent the name of a domain object, and valid values ​​are any strings from 1 to 255 characters long, inclusive. We could represent this using the following structure:

 public struct ValidatedName : IEquatable<ValidatedName> { private readonly string _value; private ValidatedName(string name) { _value = name; } public static bool IsValid(string name) { return !String.IsNullOrEmpty(name) && name.Length <= 255; } public bool Equals(ValidatedName other) { return _value == other._value; } public override bool Equals(object obj) { if (obj is ValidatedName) { return Equals((ValidatedName)obj); } return false; } public static implicit operator string(ValidatedName x) { return x.ToString(); } public static explicit operator ValidatedName(string x) { if (IsValid(x)) { return new ValidatedName(x); } throw new InvalidCastException(); } public static bool operator ==(ValidatedName x, ValidatedName y) { return x.Equals(y); } public static bool operator !=(ValidatedName x, ValidatedName y) { return !x.Equals(y); } public override int GetHashCode() { return _value.GetHashCode(); } public override string ToString() { return _value; } } 

This example shows the conversion of to- string as implicit , since it never fails, but from- string distinguishes it as explicit , as this will lead to invalid values, but, of course, it can be either implicit or explicit .

Note also that you can only initialize this structure using a cast from string , but you can check whether such a translation will be possible in advance using the IsValid static method.

It would seem that this is a good template for checking the validity of domain values ​​that can be represented by simple types, but I don’t see it being often used or suggested, and I wonder why.

So my question is: what do you see as the advantages and disadvantages of using this template and why?

If you feel that this is a bad template, I would like to understand why, as well as what you consider to be the best alternative.

+6
source share
2 answers

Your path is a fair bit of code, you had to add equality testing and casting when your intentions were verified.

Where are you if you need validatedname2, which cannot be empty or whitespace and must be 8 to 16 characters long without punctuation, then ValdatedNameType3 ...

Mine will use attributes, for example. allowed_blank and maximum length or maybe even regular expression

0
source

Your path is quite hard and intense. I usually define domain objects, for example:

 public class Institution { private Institution() { } public Institution(int organizationId, string name) { OrganizationId = organizationId; Name = name; ReplicationKey = Guid.NewGuid(); new InstitutionValidator().ValidateAndThrow(this); } public int Id { get; private set; } public string Name { get; private set; } public virtual ICollection<Department> Departments { get; private set; } ... other properties public Department AddDepartment(string name) { var department = new Department(Id, name); if (Departments == null) Departments = new List<Department>(); Departments.Add(department); return department; } ... other domain operations } 

In the entity constructor, validation is performed using FluentValidation.NET to ensure that you cannot create an object with an invalid state. Please note that all properties are read-only - you can set them only through the constructor or dedicated domain operations.

Validation of this object is a separate class:

 public class InstitutionValidator : AbstractValidator<Institution> { public InstitutionValidator() { RuleFor(institution => institution.Name).NotNull().Length(1, 100).WithLocalizedName(() => Prim.Mgp.Infrastructure.Resources.GlobalResources.InstitutionName); RuleFor(institution => institution.OrganizationId).GreaterThan(0); RuleFor(institution => institution.ReplicationKey).NotNull().NotEqual(Guid.Empty); } } 

These validators can also be easily reused and you write less boilerplate code.

0
source

Source: https://habr.com/ru/post/980589/


All Articles