2016-08-16 7 views
1

Представьте себе следующий класс, используемый в качестве объекта параметров для некоторого сложного запроса.Использование изменяемого объекта в качестве хеш-ключа

public class RequestParameters 
{ 
    ///<summary>Detailed User-Friendly Descriptions.</summary> 
    string Param1 { get; set; } = "Default"; 
    int? Param2 { get; set; } 
    bool Param3 { get; set; } = true; 
    ... 
    string ParamN { get; set; } 
} 

Я хочу, чтобы пользователи могли создавать и настраивать эти параметры объекта, как они считают нужным, прежде чем передать его для использования в запросе:

RequestParameters _filtered = new RequestParameters(); 
filtered.Param1 = "yellow"; 
filtered.Param3 = true; 
_someInstance.InvokeRequest(_filtered); 

Когда я обработки запроса, я хотите кэшировать результат на основе предоставленных параметров запроса. Было бы тривиально реализовать некоторый метод клонирования и IEquatable<RequestParameters>, и переопределить Equals() и GetHashCode() по вышеуказанному классу RequestParameters, но последнее считается широко распространенным методом - RequestParameters является изменяемым, и если экземпляр был изменен после вставки, он может испортиться мой кеш.

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

Неужели было бы так плохо использовать этот класс в качестве ключа кэш-памяти, если я стараюсь делать копии экземпляров, которые пользователи отправляют, и никогда не изменять их после вставки в кеш? Какие альтернативы вы бы предложили, если бы вы были разработчиком, работающим с одними и теми же объектами?

+1

Как насчет какого-то завода, где вы собираете все параметры, возможно, меняя их по пути и возвращение неотменяемая копия их по требованию? Что касается «дублирования кода» и «головной боли обслуживания»: как насчет использования T4 для сохранения свойств чтения/записи фабрики и свойств readonly неизменяемой копии в синхронизации? – Corak

+1

У вас есть три варианта. (1) летать на сиденье ваших штанов и просто использовать объект как есть, и надеяться, что никто не мутирует его. (2) Внедрите метод '.Freeze()', который предотвращает дальнейшие изменения объекта, чтобы вы могли положиться на 'GetHashCode' /' Equals'. (3) Создайте версию вашего класса только для чтения. – Enigmativity

+1

Еще одна проблема с внедрением GetHashCode() над изменяемыми полями - это часть открытого интерфейса (общедоступного) класса. Клиенты могут захотеть хранить экземпляры в своих собственных словарях или наборах HashSets, и они могут ожидать поведения эталонного равенства, учитывая, что класс изменен. – Blorgbeard

ответ

1

В духе предложения Матовый, в один из возможных решений (которые я могу в конечном итоге использование) является компромиссом решения AsReadOnly() (что требует дублирования структуры класса и большого количества копий/вставки).

Если основная цель этого класса - удобная для пользователя параметризация, и мы все в порядке с его использованием в качестве ключа кэша, являющегося гражданином второго сорта, относительно простым способом его реализации является создание метода GetHashKey(). Весь этот метод должен сделать, это вернуть неизменный объект, на который мы можем ссылаться «Equals» и «GetHashCode», и гарантировать, что эквивалентные экземпляры этого объекта будут обрабатываться равными. Кортеж сделать это, но так делают AnonymousTypes, которые являются компактнее и проще создать для более 6 параметров участвуют:

/// <summary>Get a hashable/comparable key representing the current parameters.</summary> 
/// <returns>The hash key.</returns> 
public virtual object GetHashKey() 
{ 
    return new { Param1, Param2, Param3, Param4, ..., ParamN }; 
} 

Возвращенный AnonymousType правильно реализует

  • GetHashCode() как комбинированный хэш каждого значения в объекте.
  • Equals() как количественное сравнение каждого значения в объекте.

Используя это, мы в основном создаем моментальный снимок (копию) объекта RequestParameters в его текущем состоянии. Мы жертвуем способностью легко отражать свойства, но упрощаем и надежно их использовать в качестве ключа кеша Equatable/Hashable, не требуя дополнительных классов.

В моем конкретном случае, избегая дополнительных классов, особенно важно, поскольку у меня нет только одного класса параметров, у меня есть десятки. Более того, многие классы RequestParameter наследуют друг от друга (так как многие запросы имеют одни и те же базовые параметры). Создание целого набора «ReadOnly» версий этих классов было бы сложным.В этом решении, каждый просто должен переопределить GetHashKey() на пару своих базовые классов AnonymousType со своими дополнительными параметрами:

public class AdditionalParameters : RequestParameters 
{ 
    ///<summary>Detailed User-Friendly Descriptions.</summary> 
    int? Param42 { get; set; }; 
    ... 
    string Param70 { get; set; } 

    public override object GetHashKey() 
    { 
     return new { base.GetHashKey(), Param42, ..., Param70 }; 
    } 
} 
-1

Вы всегда можете сохранить параметры в кортеже, как только вы собрали их, как неизменен после создания ....

+0

Привет, Спасибо за предложение (я поддержал, чтобы противостоять нисходящему кому-то другому.) Итак, вы думаете, как метод GetHashKey(), который возвращает Tuple? Это неплохая идея. Я думаю, что проблема с этим предложением состоит в том, что у меня есть десятки параметров, поэтому создание N-Tuple (что потребует вложенных кортежей после 6-го параметра) может стать уродливым, не говоря уже о том, что я потеряю хорошую объектно-ориентированную работу с классом с именованными свойствами. Тем не менее, в целом, не страшная идея. – Alain