65.9K
CodeProject 正在变化。 阅读更多。
Home

通用Trictionary类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.53/5 (18投票s)

2009年3月11日

CPOL

4分钟阅读

viewsIcon

56745

downloadIcon

159

本文介绍了一个通用的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 用法的开发人员来说更加直观。在上面的示例中,与值关联的两个类型是 stringdouble。您只需通过索引器(就像您通常使用 Dictionary 类一样)分配 stringdouble 值。检索仅仅是将它转换为 stringdouble。可能有些人会认为 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
}

隐式转换很自然就明白了。这些运算符允许我(在示例中,即 stringdouble)将任一类型传递给 Trictionary。它们还允许我将 DualObject<> 转换回任一类型(在我的示例代码中是 stringdouble)。最有趣的可能是我的 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));

上面的代码将按预期输出 TrueFalseTrue。如果我选择不以这种方式实现 Equals,我将不得不重新实现 ContainsValue 并重写大量的比较代码(这是我想要避免的)。此外,我不期望任何人会在 Trictionary 之外使用我的 DualObject<> 类。即使他们这样做了,我认为以这种方式保持 Equals 的行为实际上可能会帮助他们。

请随时发表您的评论和反馈。我想特别提到,任何关于需要 Trictionary 类型类的反馈都应该转给 Joe Enos,而不是我 *微笑*

[请注意,zip 文件中的可下载代码已正确格式化,因为我没有添加额外的换行符来改进浏览器显示]

参考文献

历史

  • 2009 年 3 月 11 日 - 文章发布
© . All rights reserved.