通用Trictionary类






4.53/5 (18投票s)
本文介绍了一个通用的Trictionary类,它派生自Dictionary,允许每个键有两个不同类型的值。
引言
我写这篇文章和配套的代码纯粹是出于好玩。我写文章已经有一段时间了,我想这有助于我重新找回感觉。这篇文章的灵感来自于另一篇关于同一主题的文章
那篇文章描述了一个 Trictionary
类,它本质上是一个字典,但对于每个键有两个值,这两个值类型不同。在许多情况下,与其这样做,更合适的做法可能是使用一个 struct
,它将这两个类型作为成员。但有时您可能也希望避免不必要地为这个目的创建一个 struct
。您也可以使用匿名类型,但这在某种意义上并没有太大区别,因为您仍然会在您的程序集中得到一个新类型。Joe Enos 的 Trictionary
类引起了我的兴趣,尽管我不喜欢该类允许的语法用法。这是他文章中的一些示例代码
Trictionary<int, string, double> trict = new Trictionary<int, string, double>();
// ADDING:
// Option 1:
trict.Add(1, "A", 1.1);
// Option 2:
trict[2] = new DualObjectContainer<string,double>("B", 2.2);
// RETRIEVING:
// Option 1:
string s;
double d;
trict.Get(2, out s, out d);
// Option 2:
DualObjectContainer<string, double> container = trict[1];
我不喜欢必须调用 Add
方法或创建一个对象才能向 Trictionary
添加条目的事实。我也不喜欢检索机制,调用一个带有两个 out
参数的 Get
方法一点也不优雅,而获取他用来存储值的子对象在我看来则更加混乱。这就是为什么我很快地组合了这个小类。我自己从未不得不使用它(鉴于我一个小时前才写好它,我还没有机会这样做),但我将来可能会使用它。
用法
这里有一些示例代码,展示了我的 Trictionary
的用法。
static void Main()
{
Trictionary<int, string, double> trictionary =
new Trictionary<int, string, double>();
trictionary[10] = 10.7;
trictionary[10] = "sss";
trictionary[10] = "sss-mod";
string s = trictionary[10];
double d = trictionary[10];
Console.WriteLine(s);
Console.WriteLine(d);
trictionary[12] = 11.4;
trictionary[12] = "bbb";
trictionary[12] = 11.5;
trictionary[15] = 10.1;
trictionary[16] = "ppp";
trictionary[19] = "bbb";
trictionary[19] = 11.5;
foreach (var value in trictionary.Values)
{
Console.WriteLine("{0}, {1}", (string)value, (double)value );
}
}
请注意,该类的使用方式对于熟悉 Dictionary
用法的开发人员来说更加直观。在上面的示例中,与值关联的两个类型是 string
和 double
。您只需通过索引器(就像您通常使用 Dictionary
类一样)分配 string
或 double
值。检索仅仅是将它转换为 string
或 double
。可能有些人会认为 Joe Enos 的类提供了更直观的用法结构——嗯,不同的人对此有不同的看法。我相信会有人更喜欢我的类的用法——所以这并不重要。
局限性
这两个值不能是相同的类型。如果是这种情况,那么您就不需要这个类——您可以使用常规 Dictionary
类的 List<T>
或 ICollection<T>
作为值类型。这是“设计如此”。以下代码将无法编译。
Trictionary<int, string, string> trictionary2 =
new Trictionary<int, string, string>();
trictionary2[1] = "hello";
trictionary2[2] = "world"
实现细节
我的 Trictionary
类继承自 Dictionary
。以下是完整的类列表(已重新格式化以适应浏览器宽度)
/// <summary>
/// Represents a dictionary with two distinct typed values per key
/// </summary>
/// <typeparam name="TKey">The type of the dictionary key</typeparam>
/// <typeparam name="TValue1">The type of the first value</typeparam>
/// <typeparam name="TValue2">The type of the second value</typeparam>
[Serializable]
public class Trictionary<TKey, TValue1, TValue2>
: Dictionary<TKey, DualObject<TValue1, TValue2>>
{
/// <summary>
/// Initializes a new instance of the Trictionary class
/// </summary>
public Trictionary()
{
}
/// <summary>
/// Initializes a new instance of the Trictionary
/// class for use with serialization
/// </summary>
/// <param name="info">SerializationInfo objects that holds the
/// required information for serialization</param>
/// <param name="context">StreamingContext structure
/// for serialization</param>
protected Trictionary(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
/// <summary>
/// Gets or sets the values associated with the specified key
/// </summary>
/// <param name="key">The key of the values to get or set</param>
/// <returns>Returns the DualObject associated with this key</returns>
public new DualObject<TValue1, TValue2> this[TKey key]
{
get
{
return base[key];
}
set
{
if (this.ContainsKey(key))
{
base[key].Set(value);
}
else
{
base[key] = value;
}
}
}
}
我试图用粗体标出代码中的重要部分,但这取决于使用的字体,可能并不显眼。本质上,字典的值类型是我的 DualObject<>
类,我稍后会讨论它。如果这是第一次使用该键,它将仅将其添加到字典中,否则它将使用 DualObject
类中的 Set
方法来关联新类型的值。那么,当字典的值类型是 DualObject<>
类时,它如何接受任一类型的值呢?这都是隐式运算符的魔力,如下所示。[代码已重新格式化以适应浏览器]
/// <summary>
/// Represents a class that can hold two types values
/// </summary>
/// <typeparam name="TValue1">The first type</typeparam>
/// <typeparam name="TValue2">The second type</typeparam>
[Serializable]
public class DualObject<TValue1, TValue2>
: IEquatable<DualObject<TValue1, TValue2>>
{
/// <summary>
/// Initializes a new instance of the DualObject class
/// </summary>
/// <param name="value1">First value</param>
/// <param name="value2">Second value</param>
public DualObject(TValue1 value1, TValue2 value2)
{
this.FirstValue = value1;
this.SecondValue = value2;
}
/// <summary>
/// Initializes a new instance of the DualObject class
/// </summary>
/// <param name="value2">Second value</param>
/// <param name="value1">First value</param>
public DualObject(TValue2 value2, TValue1 value1)
: this(value1, value2)
{
}
/// <summary>
/// Initializes a new instance of the DualObject class
/// </summary>
/// <param name="value">First value</param>
public DualObject(TValue1 value)
{
this.FirstValue = value;
}
/// <summary>
/// Initializes a new instance of the DualObject class
/// </summary>
/// <param name="value">Second value</param>
public DualObject(TValue2 value)
{
this.SecondValue = value;
}
private bool isFirstValueSet;
private TValue1 firstValue;
private TValue1 FirstValue
{
get
{
return this.firstValue;
}
set
{
this.firstValue = value;
this.isFirstValueSet = true;
}
}
private bool isSecondValueSet;
private TValue2 secondValue;
private TValue2 SecondValue
{
get
{
return this.secondValue;
}
set
{
this.secondValue = value;
this.isSecondValueSet = true;
}
}
/// <summary>
/// Implicit conversion operator to convert to T1
/// </summary>
/// <param name="dualObject">The DualObject to convert from</param>
/// <returns>The converted object</returns>
public static implicit operator TValue1(
DualObject<TValue1, TValue2> dualObject)
{
return dualObject.FirstValue;
}
/// <summary>
/// Implicit conversion operator to convert to T2
/// </summary>
/// <param name="dualObject">The DualObject to convert from</param>
/// <returns>The converted object</returns>
public static implicit operator TValue2(
DualObject<TValue1, TValue2> dualObject)
{
return dualObject.SecondValue;
}
/// <summary>
/// Implicit conversion operator to convert from T1
/// </summary>
/// <param name="value">The object to convert from</param>
/// <returns>The converted DualObject</returns>
public static implicit operator DualObject<TValue1, TValue2>(
TValue1 value)
{
return new DualObject<TValue1, TValue2>(value);
}
/// <summary>
/// Implicit conversion operator to convert from T2
/// </summary>
/// <param name="value">The object to convert from</param>
/// <returns>The converted DualObject</returns>
public static implicit operator DualObject<TValue1, TValue2>(
TValue2 value)
{
return new DualObject<TValue1, TValue2>(value);
}
/// <summary>
/// Sets the value for the first type
/// </summary>
/// <param name="value">The value to set</param>
public void Set(TValue1 value)
{
this.FirstValue = value;
}
/// <summary>
/// Sets the value for the second type
/// </summary>
/// <param name="value">The value to set</param>
public void Set(TValue2 value)
{
this.SecondValue = value;
}
/// <summary>
/// Sets the values from another DualObject
/// </summary>
/// <param name="dualObject">The DualObject
/// to copy the values from</param>
public void Set(DualObject<TValue1, TValue2> dualObject)
{
if (dualObject.isFirstValueSet)
{
this.FirstValue = dualObject.FirstValue;
}
if (dualObject.isSecondValueSet)
{
this.SecondValue = dualObject.SecondValue;
}
}
#region IEquatable<DualObject<T1,T2>> Members
/// <summary>
/// Indicates whether the current DualObject is
/// equal to another DualObject
/// Note : This will return true if either one of the
/// values are equal.
/// </summary>
/// <param name="other">The DualObject to compare with</param>
/// <returns>True if the objects are equal</returns>
public bool Equals(DualObject<TValue1, TValue2> other)
{
bool firstEqual = this.FirstValue == null ?
other.FirstValue == null :
this.FirstValue.Equals(other.FirstValue);
bool secondEqual = this.SecondValue == null ?
other.SecondValue == null :
this.SecondValue.Equals(other.SecondValue);
return firstEqual || secondEqual;
}
#endregion
}
隐式转换很自然就明白了。这些运算符允许我(在示例中,即 string
和 double
)将任一类型传递给 Trictionary
。它们还允许我将 DualObject<>
转换回任一类型(在我的示例代码中是 string
或 double
)。最有趣的可能是我的 Equals
实现,乍一看可能显得错误。那个 ||
比较应该是 &&
比较吗?技术上说是的,它应该是,但为了我的 Trictionary
类,两个 DualObject<>
对象被视为相等,如果它们的两个值中的任何一个匹配。这是因为我希望 ContainsValue
正常工作。以下代码将清楚地说明这一点
trictionary.Clear();
trictionary[19] = "bbb";
trictionary[19] = 11.5;
Console.WriteLine(trictionary.ContainsValue("bbb"));
Console.WriteLine(trictionary.ContainsValue("aaa"));
Console.WriteLine(trictionary.ContainsValue(11.5));
上面的代码将按预期输出 True
、False
、True
。如果我选择不以这种方式实现 Equals
,我将不得不重新实现 ContainsValue
并重写大量的比较代码(这是我想要避免的)。此外,我不期望任何人会在 Trictionary
之外使用我的 DualObject<>
类。即使他们这样做了,我认为以这种方式保持 Equals
的行为实际上可能会帮助他们。
请随时发表您的评论和反馈。我想特别提到,任何关于需要 Trictionary
类型类的反馈都应该转给 Joe Enos,而不是我 *微笑*
[请注意,zip 文件中的可下载代码已正确格式化,因为我没有添加额外的换行符来改进浏览器显示]
参考文献
- Trictionary - 多值字典 作者:Joe Enos(导致本文的文章)。
历史
- 2009 年 3 月 11 日 - 文章发布