Техника, на которую вы ссылаетесь, называется "const
-корректность" , которая является языковой особенностью C ++ и Swift, но, к сожалению, не C # - однако вы что-то делаете, используяпользовательский атрибут, потому что таким образом вы можете применить его через расширение Roslyn - но это кроличья нора.
В качестве альтернативы, есть гораздо более простое решение с использованием интерфейсов: потому что C # (и я думаю, что CLR тоже) не поддерживаетconst -корректность (самый близкий у нас модификатор поля readonly
). Разработчики библиотеки базовых классов .NET добавили «интерфейсы только для чтения» к обычным изменяемым типам, чтобы позволить объекту (изменяемому или неизменяемому) показать объект своей функциональности.через интерфейс, который предоставляет только неизменные операции.Некоторые примеры включают IReadOnlyList<T>
, IReadOnlyCollection<T>
, IReadOnlyDictionary<T>
- хотя это все перечислимые типы, этот метод подходит и для единичных объектов.
Преимущество этого дизайна в том, что он работает на любом языке, который поддерживает интерфейсы, ноне константно.
- Для каждого типа (
class
, struct
и т. д.) в вашем проекте, который должен предоставлять данные без риска их изменения - или любые неизменяемые операции, а затем создавать неизменяемыеинтерфейс. - Измените код потребления, чтобы использовать эти интерфейсы вместо конкретного типа.
Примерно так:
Предположим, у нас есть изменяемый класс User
ипотребляющий сервис:
public class User
{
public String UserName { get; set; }
public Byte[] PasswordHash { get; set; }
public Byte[] PasswordSalt { get; set; }
public Boolean ValidatePassword(String inputPassword)
{
Hash[] inputHash = Crypto.GetHash( inputPassword, this.PasswordSalt );
return Crypto.CompareHashes( this.PasswordHash, inputHash );
}
public void ResetSalt()
{
this.PasswordSalt = Crypto.GetRandomBytes( 16 );
}
}
public static void DoReadOnlyStuffWithUser( User user )
{
...
}
public static void WriteStuffToUser( User user )
{
...
}
Затем создайте неизменный интерфейс:
public interface IReadOnlyUser
{
// Note that the interfaces' properties lack setters.
String UserName { get; }
IReadOnlyList<Byte> PasswordHash { get; }
IReadOnlyList<Byte> PasswordSalt { get; }
// ValidatePassword does not mutate state so it's exposed
Boolean ValidatePassword(String inputPassword);
// But ResetSalt is not exposed because it mutates instance state
}
Затем измените ваш User
класс и потребителей:
public class User : IReadOnlyUser
{
// (same as before, except need to expose IReadOnlyList<Byte> versions of array properties:
IReadOnlyList<Byte> IReadOnlyUser.PasswordHash => this.PasswordHash;
IReadOnlyList<Byte> IReadOnlyUser.PasswordSalt => this.PasswordSalt;
}
public static void DoReadOnlyStuffWithUser( IReadOnlyUser user )
{
...
}
// This method still uses `User` instead of `IReadOnlyUser` because it mutates the instance.
public static void WriteStuffToUser( User user )
{
...
}