Каков наилучший подход или альтернатива постоянным ссылкам? - PullRequest
0 голосов
/ 25 ноября 2018

Для целей этого вопроса «постоянная ссылка» - это ссылка на объект, из которого нельзя вызывать методы, которые изменяют объект или изменяют его свойства.

Я хочу что-то вроде этого:

Const<User> user = provider.GetUser(); // Gets a constant reference to an "User" object
var name = user.GetName(); // Ok. Doesn't modify the object
user.SetName("New value"); // <- Error. Shouldn't be able to modify the object

В идеале я бы помечал пользовательским атрибутом (например, [Constant]) каждый метод класса, который не изменяет экземпляр, и только эти методы можно вызывать из константы.Вызовы других методов могут привести к ошибке, если это возможно, во время компиляции.

Идея состоит в том, что я могу вернуть ссылку только для чтения и быть уверенной, что она не будет изменена клиентом.

Ответы [ 2 ]

0 голосов
/ 25 ноября 2018

Техника, на которую вы ссылаетесь, называется "const -корректность" , которая является языковой особенностью C ++ и Swift, но, к сожалению, не C # - однако вы что-то делаете, используяпользовательский атрибут, потому что таким образом вы можете применить его через расширение Roslyn - но это кроличья нора.

В качестве альтернативы, есть гораздо более простое решение с использованием интерфейсов: потому что C # (и я думаю, что CLR тоже) не поддерживаетconst -корректность (самый близкий у нас модификатор поля readonly). Разработчики библиотеки базовых классов .NET добавили «интерфейсы только для чтения» к обычным изменяемым типам, чтобы позволить объекту (изменяемому или неизменяемому) показать объект своей функциональности.через интерфейс, который предоставляет только неизменные операции.Некоторые примеры включают IReadOnlyList<T>, IReadOnlyCollection<T>, IReadOnlyDictionary<T> - хотя это все перечислимые типы, этот метод подходит и для единичных объектов.

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

  1. Для каждого типа (class, struct и т. д.) в вашем проекте, который должен предоставлять данные без риска их изменения - или любые неизменяемые операции, а затем создавать неизменяемыеинтерфейс.
  2. Измените код потребления, чтобы использовать эти интерфейсы вместо конкретного типа.

Примерно так:

Предположим, у нас есть изменяемый класс 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 )
{
    ...
}
0 голосов
/ 25 ноября 2018

Итак, это первые две идеи, которые у меня изначально были, но они не совсем решили проблему.

Использование динамических объектов:

Первая идея, которая у меня возникла, - создание Dynamic Object перехватывает все вызовы членов и выдает ошибку, если вызываемый метод не помечен пользовательским атрибутом [Constant].Этот подход проблематичен, потому что: а) у нас нет поддержки компилятора для проверки ошибок в коде (т. Е. Опечаток имени метода) при работе с динамическими объектами, которые могут привести к множеству ошибок во время выполнения;и б) я собираюсь использовать это часто, и поиск имен методов по имени каждый раз, когда вызывается метод, может оказать значительное влияние на производительность.

Использование RealProxy:

Моя вторая идея заключалась в использованииRealProxy, чтобы обернуть реальный объект и проверить вызываемые методы, но это работает только с объектами, которые наследуются от MarshalByRefObject.

...