с


Records — это новая функция в C # 9. Записи — это специальные классы, заимствованные из структур, поскольку они имеют равенство на основе значений. Вы можете рассматривать их как гибрид двух категорий типов. По умолчанию они более или менее неизменяемы и содержат синтаксический сахар, чтобы сделать объявление более легким и кратким. Однако синтаксический сахар может скрыть более стандартные задачи, такие как изменение поведения конструктора по умолчанию. В некоторых случаях вам, вероятно, потребуется сделать это для проверки. В этой статье показано, как этого добиться.

Возьмите этот простой пример класса:

[pastacode lang=»c» manual=»public%20class%20StringValidator%0A%7B%0A%20%20%20%20public%20string%20InputString%20%7B%20get%3B%20%7D%0A%0A%20%20%20%20public%20StringValidator(string%20inputString)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20if%20(string.IsNullOrEmpty(inputString))%20throw%20new%20ArgumentNullException(nameof(inputString))%3B%0A%0A%20%20%20%20%20%20%20%20InputString%20%3D%20inputString%3B%0A%20%20%20%20%7D%0A%7D» message=»» highlight=»» provider=»manual»/]

Понятно, что если потребитель попытается создать экземпляр этого класса без допустимой строки, он получит исключение. Стандартный синтаксис для создания записи выглядит так:

[pastacode lang=»c» manual=»%20%20%20%20public%20record%20StringValidator(string%20InputString)%3B» message=»» highlight=»» provider=»manual»/]

Он понятен и краток, но не сразу понятно, как проверить строку. Это определение сообщает компилятору, что будет свойство с именем InputString, и конструктор передаст значение этому свойству из параметра. Нам нужно удалить синтаксический сахар, чтобы проверить строку. К счастью, это легко. Нам не нужно использовать новый синтаксис для определения наших записей. Мы можем определить запись аналогично классу, но изменить ключевое слово class на record.

[pastacode lang=»c» manual=»public%20record%20StringValidator%0A%7B%0A%20%20%20%20public%20string%20InputString%20%7B%20get%3B%20%20%7D%0A%0A%20%20%20%20public%20StringValidator(string%20inputString)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20if%20(string.IsNullOrEmpty(inputString))%20throw%20new%20ArgumentNullException(nameof(inputString))%3B%0A%0A%20%20%20%20%20%20%20%20InputString%20%3D%20inputString%3B%0A%20%20%20%20%7D%0A%7D» message=»» highlight=»» provider=»manual»/]

К сожалению, это означает, что мы не можем использовать неразрушающую мутацию. Ключевое слово with дает нам возможность создать новую версию записи с измененными некоторыми свойствами. Это означает, что мы не изменяем исходный экземпляр записи, но получаем его копию. Это общий подход для программирования Fluent API и функционального стиля. Это позволяет нам сохранять неизменность.

Чтобы разрешить неразрушающую мутацию, нам нужно добавить аксессор свойства init. Он работает аналогично конструктору, но вызывается только во время инициализации объекта. Вот более полное решение, реализующее аксессор init. Это позволяет использовать общую логику конструктора и логику инициализации.

[pastacode lang=»c» manual=»using%20System%3B%0A%0Anamespace%20ConsoleApp25%0A%7B%0A%20%20%20%20class%20Program%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20static%20void%20Main(string%5B%5D%20args)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2FThis%20throws%20an%20exception%20from%20the%20constructor%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2Fvar%20stringValidator%20%3D%20new%20StringValidator(null)%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20var%20stringValidator1%20%3D%20new%20StringValidator(%22First%22)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20var%20stringValidator2%20%3D%20stringValidator1%20with%20%7B%20InputString%20%3D%20%22Second%22%20%7D%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20Console.WriteLine(stringValidator2.InputString)%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2FThis%20throws%20an%20exception%20from%20the%20init%20accessor%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2Fvar%20stringValidator3%20%3D%20stringValidator1%20with%20%7B%20InputString%20%3D%20null%20%7D%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2FOutput%3A%20Second%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%0A%20%20%20%20public%20record%20StringValidator%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20private%20string%20inputString%3B%0A%0A%20%20%20%20%20%20%20%20public%20string%20InputString%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20get%20%3D%3E%20inputString%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20init%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2FThis%20init%20accessor%20works%20like%20the%20set%20accessor%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ValidateInputString(value)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20inputString%20%3D%20value%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20public%20StringValidator(string%20inputString)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20ValidateInputString(inputString)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20InputString%20%3D%20inputString%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20public%20static%20void%20ValidateInputString(string%20inputString)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(string.IsNullOrEmpty(inputString))%20throw%20new%20ArgumentNullException(nameof(inputString))%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%7D» message=»» highlight=»» provider=»manual»/]

Должны ли конструкторы записей иметь логику?

Это спорная дискуссия, выходящая за рамки данной статьи. Многие люди возразят, что не следует помещать логику в конструкторы. Дизайн записей побуждает вас не помещать логику в конструктор или средство доступа init. Вообще говоря, записи должны отражать состояние ваших данных во времени. Вам не нужно применять логику, потому что предполагается, что вы знаете состояние своих данных на данный момент. Однако, как и в случае с любой другой конструкцией программирования, невозможно узнать, какие варианты использования могут возникать из записей. Вот пример из библиотеки Urls, которая рассматривает URL-адреса как неизменяемые записи:

[pastacode lang=»c» manual=»using%20System.Net%3B%0A%0Anamespace%20Urls%0A%7B%0A%20%20%20%20public%20record%20QueryParameter%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20private%20string%3F%20fieldValue%3B%0A%0A%20%20%20%20%20%20%20%20public%20string%20FieldName%20%7B%20get%3B%20init%3B%20%7D%0A%20%20%20%20%20%20%20%20public%20string%3F%20Value%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20get%20%3D%3E%20fieldValue%3B%20init%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fieldValue%20%3D%20WebUtility.UrlDecode(value)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20public%20QueryParameter(string%20fieldName%2C%20string%3F%20value)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20FieldName%20%3D%20fieldName%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20fieldValue%20%3D%20WebUtility.UrlDecode(value)%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20public%20override%20string%20ToString()%0A%20%20%20%20%20%20%20%20%20%20%20%20%3D%3E%20%24%22%7BFieldName%7D%7B(Value%20!%3D%20null%20%3F%20%22%3D%22%20%3A%20%22%22)%7D%7BWebUtility.UrlEncode(Value)%7D%22%3B%0A%20%20%20%20%7D%0A%7D%0A» message=»» highlight=»» provider=»manual»/]

Мы гарантируем, что декодируем значение запроса при его сохранении, а затем кодируем его, когда используем его как часть URL-адреса. Вы могли бы задать вопрос: почему бы не сделать все записи? Похоже, что с этим связаны подводные камни, но мы идем на дальше, и нам еще предстоит наметить лучшие практики для записей в контексте C #.

Подведение итогов

Разработчикам потребуется несколько лет, чтобы смириться с записями и заложить основные правила их использования. В настоящее время у вас есть чистый лист, и вы можете экспериментировать, пока «эксперты» не скажут вам обратное. Мой совет — использовать записи только для представления фиксированных данных и минимальной логики. По возможности используйте синтаксический сахар. Однако есть очевидные сценарии, в которых минимальная проверка в конструкторе может быть практичной. Используйте свое суждение, обсудите со своей командой и взвесьте все за и против.