字典Dictionary<TKey, TValue>相信大家都用过,但是如果字典的键是一个TKey数组(或者IList<TKey>),怎么办?
这就是今天要讨论的案例:MultiKeyDictionary<TKey, TValue>
先来看看反例:
public
class
MultiKeyDictionary
<
TKey, TValue
>
: IDictionary
<
TKey[], TValue
>
{
private
static
readonly
List
<
Type
>
COLUMNTYPELIST;
private
const
string
HASHCODECOLNAME
=
"
HASHCODE
"
;
private
const
string
KEYCOLMNFORMAT
=
"
KEY{0}
"
;
private
const
int
MAXPRIMARYKEYCOUNT
=
0x1f
;
private
int
_keyCount;
private
DataTable _dataTable;
private
TKey _defaultKeyValue;
private
Dictionary
<
string
, TValue
>
_hashTable;
private
MultiKeyDictionary()
{
this
._keyCount
=
0
;
this
._dataTable
=
null
;
this
._hashTable
=
null
;
this
._defaultKeyValue
=
default
(TKey);
if
(
!
MultiKeyDictionary
<
TKey, TValue
>
.COLUMNTYPELIST.Contains(
typeof
(TKey)))
{
throw
new
ArgumentException(
"
"
,
typeof
(TKey).Name));
}
}
public
MultiKeyDictionary(
int
keyCount, TKey defaultKeyValue) :
this
()
{
if
(keyCount
<=
0
)
{
throw
new
ArgumentOutOfRangeException(
"
keyCount
"
);
}
if
(defaultKeyValue
==
null
)
{
throw
new
ArgumentNullException(
"
defaultKeyValue
"
);
}
this
._keyCount
=
keyCount;
this
._dataTable
=
this
.CreateDataTable();
this
._hashTable
=
new
Dictionary
<
string
, TKey
>
();
this
._defaultKeyValue
=
defaultKeyValue;
}
public
void
Add(TKey[] keyList, TValue value)
{
if
((((keyList
==
null
)
||
(keyList.Length
>
this
._keyCount))
?
1
:
0
)
!=
0
)
{
throw
new
ArgumentOutOfRangeException(
"
keyList
"
);
}
if
(
this
.ContainsKey(aoKeyList))
{
throw
new
ArgumentException(
"
"
);
}
DataRow dr
=
this
._dataTable.NewRow();
for
(
int
i
=
0
; i
<
this
._keyCount; i
++
)
{
TKey key
=
default
(TKey);
TKey value
=
key;
if
(i
<
keyList.Length)
{
value
=
keyList[i];
}
else
{
value
=
this
._defaultKeyValue;
}
dr[i]
=
value;
}
string
hashCode
=
Guid.NewGuid().ToString();
dr[
"
HASHCODE
"
]
=
hashCode;
this
._dataTable.Rows.Add(dr);
this
._hashTable.Add(hashCode, value);
}
//
此处省去600行代码
}
第一次看到这个代码的时候,我也傻眼了,没想到这个给竟然能写成这样。里面用DataTable来放多个键值,并且,规定前面的每一个列是主键,到最后一 列,放一个Guid的ToString,然后再通过Dictionary<string, TValue>来查找对应的值。
说说这么实现的不足之处吧(好像没什么必要。。。),第一用DataTable的话,效率将被极大的降低,第二,必须要预先告知数组的最大长度,如果,定多了影响性能,定少了,没法加数据。
下面说说正确的实现方式吧。
第一、MultiKeyDictionary<TKey, TValue>是Dictionary<TKey, TValue>的一种特例,相当于把Dictionary<TKey, TValue>中的TKey替换成TKey[],所以应该让MultiKeyDictionary<TKey, TValue>继承Dictionary<TKey[], TValue>。
第二、剩下来的问题是,如何修改TKey[]的判等,如果Dictionary<TKey[], TValue>没有留下这个扩展,当然就没法直接利用继承了。看一下Dictionary<TKey, TValue>的构造函数,不难发现有一个public Dictionary(IEqualityComparer<TKey> comparer)的构造,IEqualityComparer<TKey>是个什么样的接口?它包含bool Equals(T x, T y);int GetHashCode(T obj);两个方法,也就是通过它,可以修改对象判等(包括GetHashCode)。
第三、到这里,相信已经比较明确了,写一个类实现IEqualityComparer<TKey[]>接口,在构造 MultiKeyDictionary<TKey, TValue>时调用Dictionary<TKey[], TValue>的带参构造,将一个符合IEqualityComparer<TKey[]>接口的对象传入,就完成了一个这个 MultiKeyDictionary,并且,拥有所有Dictionary<TKey[], TValue>的功能,如果需要,还可以添加自己的成员。
最后,有一个注意点,GetHashCode和Equals的相互关系:
1、GetHashCode相等,对象未必Equals
2、对象Equals,GetHashCode必定相等
3、GetHashCode不相等,对象必定不Equals
更具体的描述在
MSDN