7

I find myself occasionally in C# 3.0 looking for ways to simulate the notion of a tuple. Over time I've had various "poor man's" implementations, here are a few of them:

Basic Object Array:

object[] poorTuple = new object[]{foo,bar,baz}; // basic object array

More Strongly Typed, HoHoHo...

KeyValuePair<TypeA, KeyValuePair<TypeB, TypeC>> poorTuple;

Implementing a class that can use type inference (lifted from Functional Programming for the Real World)

public static class Tuple{
  public static Tuple<T1, T2> Create<T1 foo, T2 bar>{
    return new Tuple<T1, T2>(foo, bar);
  }
}
// later: 
var data = Tuple.Create("foo", 42);

Questions:

  1. Any other ways to have a poor man's tuple in C# 3.0 (or language of choice that lacks the data structure).

  2. What is the best way to get a tuple in C# 3.0 - if anyone has a library recommendation it is welcome.

  3. At what point (yes, generalize for me) does it make sense to create a specific type rather than something like a list or tuple? (looking for rules of thumb)

4

7 回答 7

8

您可以创建功能类似于元组的匿名类型,除了有用的名称:

var itemsWithChildCounts 
    = myObjects.Select(x => new { Name = x.Name, Count = x.Children.Count() });
于 2009-11-30T22:06:58.313 回答
5

下面是一个通用元组的代码,摘自 Bill Wagner在Visual Studio Magazine 2007 年 4 月版中的文章

public struct Tuple<T1, T2> : IEquatable<Tuple<T1, T2>>
{
    private readonly T1 first;
    public T1 First
    {
        get { return first; }
    }

    private readonly T2 second;
    public T2 Second
    {
        get { return second; }
    } 

    public Tuple(T1 f, T2 s)
    {
        first = f;
        second = s;
    }

    #region IEquatable<Tuple<T1,T2>> Members
    public bool Equals(Tuple<T1, T2> other)
    {
        return first.Equals(other.first) && 
            second.Equals(other.second);
    }

    public override bool Equals(object obj)
    {
        if (obj is Tuple<T1, T2>)
            return this.Equals((Tuple<T1, T2>)obj);
        else
            return false;
    }

    public override int GetHashCode()
    {
        return first.GetHashCode() ˆ second.GetHashCode();
    }
    #endregion
}
于 2009-12-01T15:12:04.130 回答
1

对于 1 - 2:我更喜欢实现自己的元组类。您窃取的实现是一个不错的实现。它应该运作良好。

对于 3:这是我的经验法则 - 一旦您要在多个方法中重用此功能(具有相同含义的相同类型),或者如果您在任何公共 API 中使用它,我认为是时候实现了具有您特定类型的“真实”类。

于 2009-11-30T22:11:20.163 回答
0

我认为在您的程序中引入一个新值集有意义时,创建一个新类型是件好事。例如,如果您正在编写一个复杂的计算器,那么请创建一个复数类型。如果您真的只是想暂时将几个变量“粘合”在一起,那么元组可能是更好的选择。假设您有一个从控制台收集两个数字的简单函数……您可能会执行以下操作:

static void GetNumbers(out int x, out int y) { ... }
...
int x, y;
GetNumbers(out x, out y);

我认为当一个“getter”函数有一个返回值时通常是有意义的,但在这种情况下它没有,因为我真的不能有两个返回值。我可以去创建一个名为 TwoNumbers 的新类型并使用它,但我认为这很快就会成为一个问题而不是解决方案。如果 C# 有元组,我可以执行以下操作:

static (int, int) GetNumbers() { ... }
...
int x, y;
(x, y) = GetNumbers();

现在真正有趣的问题是:虽然 C# 没有这个特性,我能用库自己实现它吗?您建议的代码是一个开始,但不允许我像在第二个示例中所做的那样分配。我不确定 C#,但你可以在 C++ 中非常接近这一点,因为当函数调用的返回值是引用类型时,它可以是赋值运算符的左操作数。考虑以下:

// this class is implemented much like yours only with C++
template<typename T1, typename T2> class tuple { ... }
...
template<typename T1, typename T2> tuple<T1, T2>& tie(T1 a, T2 b) { ... }
...
template<typename T1, typename T2> tuple<T1, T2> get_numbers() { ... }
...
int x, y;
tie(x, y) = get_numbers();

我会说这非常接近......而不是(x, y) =我们有tie(x, y) =。如果您对实现细节感兴趣,我会将您推荐给我第一次了解这一点的 TR1 库:

http://www.boost.org/doc/libs/1_41_0/libs/tuple/doc/tuple_users_guide.html#tiers

因此,要将这一切带回到 C# 3.0... 我们当然可以创建一个如您所展示的通用元组类,但是我们可以创建一个类似 tie 的函数来“解包”C# 中的元组吗?没试过,感觉挺好玩的 如果没有这样的东西,我会不愿意让 C# 元组库在我的代码中激增。

于 2009-12-16T02:00:57.547 回答
0
  1. “实现一个类”方法和它将会得到的一样好(无论如何它应该在.NET的下一个版本中)
  2. 不知道,我现在将它们保存在我自己的个人课程库中。
  3. 我会考虑在它们公开后立即为它们创建一个适当的类(即,当它们不再用于在内部存储类的相关值时)。
于 2009-11-30T22:12:15.887 回答
0

既然你征求意见,我的意见是总是创造一种类型——我想不出不这样做的理由。

通常情况下,您会发现您实际上需要该类型(主要用途是将两个项目存储在集合中或从方法调用中返回两个项目 - 在这两种情况下,如果项目不密切相关,您重新可能做错了什么)。

于 2009-11-30T22:15:25.990 回答
-2

我不喜欢 C# 中的类型推断,因为它被滥用于更短的写作。当然这是一个非常酷的功能,但在这种情况下,人们滥用它恕我直言。

在这种情况下,我会明确指定类型以避免混淆类型(即,42 可能是长、字节或短)。

那么为什么不拥有一个简单的元组类,它可以用几行代码来实现。如果你很懒,你甚至可以为你的元组类写一些扩展方法。这使生活更轻松,但也更清洁。

不要看到有一个“花哨的”元组类而不是你提供的通用类(类型推断除外)的意义。

于 2009-11-30T22:07:39.673 回答