.NET Framework 3.5中与Tuple (.NET 4)等效的对象是什么?

60

在.NET Framework 3.5中是否存在与.NET 4中的Tuple相当的类?

我想使用它来从方法返回多个值,而不是创建struct


6
简短的回答是不。 - Darius Kucinskas
3
我越多地使用元组,就越喜欢专门的结构体——当你记不起其中包含什么内容时,.Item1、.Item2 的歧义有点让人恼火。 - Joe
2
为什么不返回一个具有您想要返回的公共成员的类实例,而不是结构体呢?我认为这样可以使一切更加自我说明。虽然结构体也可以,但我个人更喜欢类而不是结构体。 - ingredient_15939
有点愚蠢的问题,但是KeyValuePair<T1, T2>有什么问题吗? - War
1
@Wardy KeyValuePair 意味着一个 与一个 相关联。这不是我当时想要的。 - Otiel
显示剩余2条评论
6个回答

85

在 .Net 3.5 中是不支持的。但是自己创建应该不难。

public class Tuple<T1, T2>
{
    public T1 First { get; private set; }
    public T2 Second { get; private set; }
    internal Tuple(T1 first, T2 second)
    {
        First = first;
        Second = second;
    }
}

public static class Tuple
{
    public static Tuple<T1, T2> New<T1, T2>(T1 first, T2 second)
    {
        var tuple = new Tuple<T1, T2>(first, second);
        return tuple;
    }
}

更新: 将静态内容移动到静态类中以允许类型推断。使用此更新,您可以编写如 var tuple = Tuple.New(5, "hello"); 的内容,并且它会隐式地自动修复类型。


一、你没有提供Equals实现,这是.NET 4.0元组所提供的;二、你在最后一个案例中谈论的是类型推断而不是隐式转换。 - nawfal
@nawfal,感谢您关于类型推断的建议。关于等式实现,您可以参考我的评论。这取决于业务需求是否需要,我回答了具体问题,并没有像.NET 4.0中实现的那样编写完整的实现。此外,我并没有说这是元组的完整实现。 - Tomas Jansson
1
嗯,这个问题是关于.NET 4.0元组等价的。不想挑刺,但元组的可比性是初学者通常忽略的事情。那个方面实际上值得一提。 - nawfal
既然元组被视为值(因此具有私有设置器),那么这应该是一个结构体而不是类吧? - matthias_buehlmann
1
FYI,Tuple类在.NET 4.5中似乎已经发生了变化;属性现在被称为Item1Item2,而不是FirstSecond - ashes999
显示剩余3条评论

26

我在我的4.0版本之前的项目中使用了这个:

public class Tuple<T1>  
{ 
    public Tuple(T1 item1) 
    { 
        Item1 = item1; 
    }   

    public T1 Item1 { get; set; }  
} 

public class Tuple<T1, T2> : Tuple<T1>  
{ 
    public Tuple(T1 item1, T2 item2) : base(item1) 
    { 
        Item2 = item2; 
    } 

    public T2 Item2 { get; set; }  
} 

public class Tuple<T1, T2, T3> : Tuple<T1, T2>  
{ 
    public Tuple(T1 item1, T2 item2, T3 item3) : base(item1, item2) 
    { 
        Item3 = item3; 
    } 

    public T3 Item3 { get; set; }  
} 

public static class Tuple  
{ 
    public static Tuple<T1> Create<T1>(T1 item1) 
    { 
        return new Tuple<T1>(item1); 
    } 

    public static Tuple<T1, T2> Create<T1, T2>(T1 item1, T2 item2) 
    { 
        return new Tuple<T1, T2>(item1, item2); 
    } 

    public static Tuple<T1, T2, T3> Create<T1, T2, T3>(T1 item1, T2 item2, T3 item3) 
    { 
        return new Tuple<T1, T2, T3>(item1, item2, item3); 
    }  
}

11
你为什么需要 Tuple<T1>?它真的是一个元组吗? - Tomas Jansson

24

如果您希望它们与 .Net 4.0 在功能方面具有相同水平(主要是比较),

static class Tuple
{
    public static Tuple<T1, T2> Create<T1, T2>(T1 item1, T2 item2)
    {
        return new Tuple<T1, T2>(item1, item2);
    }
}

[DebuggerDisplay("Item1={Item1};Item2={Item2}")]
class Tuple<T1, T2> : IFormattable
{
    public T1 Item1 { get; private set; }
    public T2 Item2 { get; private set; }

    public Tuple(T1 item1, T2 item2)
    {
        Item1 = item1;
        Item2 = item2;
    }

    #region Optional - If you need to use in dictionaries or check equality
    private static readonly IEqualityComparer<T1> Item1Comparer = EqualityComparer<T1>.Default;
    private static readonly IEqualityComparer<T2> Item2Comparer = EqualityComparer<T2>.Default;

    public override int GetHashCode()
    {
        var hc = 0;
        if (!object.ReferenceEquals(Item1, null))
            hc = Item1Comparer.GetHashCode(Item1);
        if (!object.ReferenceEquals(Item2, null))
            hc = (hc << 3) ^ Item2Comparer.GetHashCode(Item2);
        return hc;
    }
    public override bool Equals(object obj)
    {
        var other = obj as Tuple<T1, T2>;
        if (object.ReferenceEquals(other, null))
            return false;
        else
            return Item1Comparer.Equals(Item1, other.Item1) && Item2Comparer.Equals(Item2, other.Item2);
    }
    #endregion

    #region Optional - If you need to do string-based formatting
    public override string ToString() { return ToString(null, CultureInfo.CurrentCulture); }
    public string ToString(string format, IFormatProvider formatProvider)
    {
        return string.Format(formatProvider, format ?? "{0},{1}", Item1, Item2);
    }
    #endregion
}

好答案。这就是我一直在寻找的(我正在一个项目中卡在3.5)。问题:您的GetHashCode()实现是(h1 << 3) ^ h2,而MS的实现(通过ILSpy)是(h1 << 5) + h1 ^ h2。从性能上讲,这真的有区别吗? - tigrou
@tigrou XOR 应该在使用的硅量方面略微更快(尽管它仍然是一个CPU指令)。不过,这已经是过度微观优化了 - 请随意使用任何你认为合适的方法。 - Jonathan Dickinson
我考虑的是可能的冲突,而不是计算哈希码所需的时间。 - tigrou
@tigrou 我想唯一的方法就是测试它。 - Jonathan Dickinson
这很不错,避免了装箱(即使.NET实现也没有做到)。一个小建议,你的哈希码函数中不需要空值检查,比较器已经为你完成了。 - nawfal
太棒了。话不多说。 - david.pfx

6
您可以通过Nuget安装NetLegacySupport.Tuple。这是从.Net 4.5回溯到.Net 2.0和3.5的Tuple类。
您可以通过Visual Studio中的包管理器或使用命令行上的Nuget来安装它。
以下是Nuget包的链接: https://www.nuget.org/packages/NetLegacySupport.Tuple

2

是的,但如果您想返回两个以上的值,那就不太有用了。虽然您可以将“KeyValuePairs”嵌套在其他“KeyValuePairs”中进行快速修复。 - yu_ominae
3
然而,如果你手头的内容不是“键值对”,那么更好的做法是创建自己的自定义结构体或类,或者定义一些元组类,就像其他回答中提到的一样。当你没有“键值对”时,使用暗示你有“键值对”的结构体是一种不好的做法。请避免误导后来需要阅读你代码的人! - ToolmakerSteve
抱歉给你点了踩,因为有些人可能认为这是一个有用的解决方法(尽管根据我上面的评论,我认为这是误导),但是那些提供在mono和nuget中完整元组实现链接的最新答案应该比这个答案更高。 - ToolmakerSteve

2

链接已失效。 - Dav Evans
1
更新了。 - Stefan Steiger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接