使用可变对象作为哈希键
Using a mutable object as a hash key
假设以下 class 用作某些复杂请求的参数对象。
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>
,并在上面的 RequestParameters
class 上覆盖 Equals()
和 GetHashCode()
是微不足道的,但后者被广泛认为是不好的做法 - RequestParameters
是可变的,如果实例在插入后被修改,它可能会弄乱我的缓存。
另一方面,如果我要实现此 class 的某种 ReadOnlyRequestParameters
副本,我觉得会出现大量代码重复(并且维护起来很头疼),只是没有二传手。
如果我小心翼翼地制作用户发送的实例的副本,并且一旦插入缓存就永远不会修改它们,那么使用这个 class 作为缓存键真的会很糟糕吗?如果您是使用相同对象的开发人员,您会提出哪些替代方案?
一旦你收集了参数,你总是可以将它们存储在一个元组中,因为它一旦创建就不可变....
本着 MattE 建议的精神,一种可能的解决方案(我最终可能会使用)是 AsReadOnly()
解决方案的折衷方案(需要复制 class 结构和大量 copy/pasting).
如果这个 class 的主要目标是用户友好的参数化,并且我们可以将其用作第二个 class 公民的缓存键,这是一种相对简单的方法通过创建 GetHashKey()
方法来实现它。这个方法需要做的就是 return 一个不可变的对象,我们可以在其上调用 "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 };
}
returned AnonymousType
正确实现
GetHashCode()
作为对象中每个值的组合哈希。
Equals()
作为对象中每个值的成员比较。
使用它,我们实质上生成了 RequestParameters
对象当前状态的快照(副本)。我们牺牲了轻松反映属性的能力,但可以轻松安全地将它们用作 Equatable/Hashable 缓存键,而无需任何额外的 classes.
在我的具体情况下,避免额外的 classes 尤为重要,因为我不只有 1 个参数 class,我有几十个。更重要的是,许多 RequestParameter
classes 相互继承(因为许多请求共享相同的基本参数)。创建这些 class 的一整套 "ReadOnly" 版本将是一项艰巨的任务。在此解决方案中,每个只需要重写 GetHashKey()
以将其基础 classes 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 };
}
}
假设以下 class 用作某些复杂请求的参数对象。
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>
,并在上面的 RequestParameters
class 上覆盖 Equals()
和 GetHashCode()
是微不足道的,但后者被广泛认为是不好的做法 - RequestParameters
是可变的,如果实例在插入后被修改,它可能会弄乱我的缓存。
另一方面,如果我要实现此 class 的某种 ReadOnlyRequestParameters
副本,我觉得会出现大量代码重复(并且维护起来很头疼),只是没有二传手。
如果我小心翼翼地制作用户发送的实例的副本,并且一旦插入缓存就永远不会修改它们,那么使用这个 class 作为缓存键真的会很糟糕吗?如果您是使用相同对象的开发人员,您会提出哪些替代方案?
一旦你收集了参数,你总是可以将它们存储在一个元组中,因为它一旦创建就不可变....
本着 MattE 建议的精神,一种可能的解决方案(我最终可能会使用)是 AsReadOnly()
解决方案的折衷方案(需要复制 class 结构和大量 copy/pasting).
如果这个 class 的主要目标是用户友好的参数化,并且我们可以将其用作第二个 class 公民的缓存键,这是一种相对简单的方法通过创建 GetHashKey()
方法来实现它。这个方法需要做的就是 return 一个不可变的对象,我们可以在其上调用 "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 };
}
returned AnonymousType
正确实现
GetHashCode()
作为对象中每个值的组合哈希。Equals()
作为对象中每个值的成员比较。
使用它,我们实质上生成了 RequestParameters
对象当前状态的快照(副本)。我们牺牲了轻松反映属性的能力,但可以轻松安全地将它们用作 Equatable/Hashable 缓存键,而无需任何额外的 classes.
在我的具体情况下,避免额外的 classes 尤为重要,因为我不只有 1 个参数 class,我有几十个。更重要的是,许多 RequestParameter
classes 相互继承(因为许多请求共享相同的基本参数)。创建这些 class 的一整套 "ReadOnly" 版本将是一项艰巨的任务。在此解决方案中,每个只需要重写 GetHashKey()
以将其基础 classes 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 };
}
}