在Tuple类中比“ Item1”,“ Item2”更好的命名


204

有没有一种方法可以使用Tuple类,但要提供其中的项目名称?

例如:

public Tuple<int, int, int int> GetOrderRelatedIds()

这将返回OrderGroupId,OrderTypeId,OrderSubTypeId和OrderRequirementId的ID。

让我的方法的用户知道哪个是最好的。(当您调用该方法时,结果是result.Item1,result.Item2,result.Item3,result.Item4。尚不清楚哪个是哪个。)

(我知道我可以创建一个类来容纳所有这些Id,但是如果这些Id已经拥有自己的类,并且为该方法的返回值创建类似乎很愚蠢。)


1
您将不得不自己动手- Tuple非常通用,这就是您所获得的一切
BrokenGlass 2011年

不,您不能那样做,有关更多信息,请参见此链接。msdn.microsoft.com/en
Enigma State

1
我敢说,不建议将Tuple用作api的面向公众的数据类型。我通常将Tuple用于短暂的内部事务,而不用作API的返回值。
Mike Burdick

1
此处
约翰

4
它在C#7的工作列表中,请参阅github.com/dotnet/roslyn/issues/347
Philip Ding

Answers:


277

在C#7.0(Visual Studio 2017)中,有一个新的构造可以做到这一点:

(string first, string middle, string last) LookupName(long id)

68
语法为List<(int first, int second)>。我不得不从的NuGet下载System.ValueTuple包得到它的工作在Visual Studio 2017年
马特·戴维斯

14
要创建值return (first: first, middle: middle, last: last);
法定日期为2009年

4
或仅:return (first, middle, last);在.NET 4.7.1(对于4.7.0不确定)中
watbywbarif

1
为了使用它,您将需要添加System.ValueTuple nuget软件包
Alex G

11
应该注意的是,C#7 ValueTuple虽然通常很棒,但它是一个可变值类型(结构),而Tuple它是不可变的引用类型(类)。据我所知,没有办法获得Tuple带有友好项目名称的引用类型。
dx_over_dt

51

在C#7.0之前,除了定义自己的类型外,没有其他方法可以做到。


13
我不敢相信这个答案会以40分被接受。您至少可以证明具有适当构造函数的类如何替代此答案。
bytecode77

1
@ bytecode77好吧,很快这个答案将直接出错:github.com/dotnet/roslyn/issues/347
MarkPflug

我已经看过这些语言特色建议。但是直到现在,对于更复杂的数据类型,类都是唯一的正确解决方案。您为何无论如何都要强行使用元组(请参阅其他答案)
bytecode77

3
C#7发布后,可以执行以下操作:msdn.microsoft.com/zh-cn/magazine/mt595758.aspx
BurakKarakuş16

11
Q'具有c#4作为标记,因此尽管该答案很短,但仍然正确。
史蒂夫·德雷克

33

这是您要求的过于复杂的版本:

class MyTuple : Tuple<int, int>
{
    public MyTuple(int one, int two)
        :base(one, two)
    {

    }

    public int OrderGroupId { get{ return this.Item1; } }
    public int OrderTypeId { get{ return this.Item2; } }

}

为什么不上课呢?


2
在这种情况下,结构会比类更好吗?
Deathrace

5
我看到的一点好处是,它自动实现了equals运算符,如果项均相等,则检查2个实例是否相等。
JSoet 2015年

8
这种方法的另一个缺点是Item1和Item2仍是MyTuple上的公共属性
RJFalconer

3
@deathrace Tuple本身是类,因此如果您想直接继承,Tuple<T, T2>则不能成为结构。
Chakrava

3
我可能是错的,但我一直都用元组的地方我想返回一个对象,但不希望定义一个特定的类..
杰伊

12

使用.net 4,您也许可以看一下.net ExpandoObject,但是,不要在这种简单情况下使用它,因为原本是编译时错误会变成运行时错误。

class Program
{
    static void Main(string[] args)
    {
        dynamic employee, manager;

        employee = new ExpandoObject();
        employee.Name = "John Smith";
        employee.Age = 33;

        manager = new ExpandoObject();
        manager.Name = "Allison Brown";
        manager.Age = 42;
        manager.TeamSize = 10;

        WritePerson(manager);
        WritePerson(employee);
    }
    private static void WritePerson(dynamic person)
    {
        Console.WriteLine("{0} is {1} years old.",
                          person.Name, person.Age);
        // The following statement causes an exception
        // if you pass the employee object.
        // Console.WriteLine("Manages {0} people", person.TeamSize);
    }
}
// This code example produces the following output:
// John Smith is 33 years old.
// Allison Brown is 42 years old.

别的东西值得一提的是匿名类型 的方法中,但你需要创建一个类,如果你想退货。

var MyStuff = new
    {
        PropertyName1 = 10,
        PropertyName2 = "string data",
        PropertyName3 = new ComplexType()
    };

10

本文中转载我的答案,因为它更适合这里。

启动C#7.0版本,现在它有可能举出较早使用默认像预定义的名称的元组的属性Item1Item2等等。

命名元组文字的属性

var myDetails = (MyName: "RBT_Yoga", MyAge: 22, MyFavoriteFood: "Dosa");
Console.WriteLine($"Name - {myDetails.MyName}, Age - {myDetails.MyAge}, Passion - {myDetails.MyFavoriteFood}");

控制台上的输出:

名字-RBT_Yoga,年龄-22,激情-Dosa

从方法返回元组(具有命名属性)

static void Main(string[] args)
{
    var empInfo = GetEmpInfo();
    Console.WriteLine($"Employee Details: {empInfo.firstName}, {empInfo.lastName}, {empInfo.computerName}, {empInfo.Salary}");
}

static (string firstName, string lastName, string computerName, int Salary) GetEmpInfo()
{
    //This is hardcoded just for the demonstration. Ideally this data might be coming from some DB or web service call
    return ("Rasik", "Bihari", "Rasik-PC", 1000);
}

控制台上的输出:

员工详细信息:Rasik,Bihari,Rasik-PC,1000

创建具有命名属性的元组列表

var tupleList = new List<(int Index, string Name)>
{
    (1, "cow"),
    (5, "chickens"),
    (1, "airplane")
};

foreach (var tuple in tupleList)
    Console.WriteLine($"{tuple.Index} - {tuple.Name}");

在控制台上输出:

1-牛5-鸡1-飞机

我希望我已经涵盖了所有内容。如果有任何遗漏,请在评论中提供反馈。

注意:我的代码段使用的是C#v7的字符串插值功能,如此处所述


3

MichaelMocko的回答很棒,

但我想补充一些我必须弄清楚的东西

(string first, string middle, string last) LookupName(long id)

如果您使用的是.net framework <4.7,则上述Line将给您编译时错误

因此,如果您有一个使用.net framework <4.7的项目,但仍要使用ValueTuple,而不是workAround将安装 nuget包



2

如果您的商品类型都不尽相同,我将开设一门此类,以使它们更直观。

该类的用法:

var t = TypedTuple.Create("hello", 1, new MyClass());
var s = t.Get<string>();
var i = t.Get<int>();
var c = t.Get<MyClass>();

源代码:

public static class TypedTuple
{
    public static TypedTuple<T1> Create<T1>(T1 t1)
    {
        return new TypedTuple<T1>(t1);
    }

    public static TypedTuple<T1, T2> Create<T1, T2>(T1 t1, T2 t2)
    {
        return new TypedTuple<T1, T2>(t1, t2);
    }

    public static TypedTuple<T1, T2, T3> Create<T1, T2, T3>(T1 t1, T2 t2, T3 t3)
    {
        return new TypedTuple<T1, T2, T3>(t1, t2, t3);
    }

    public static TypedTuple<T1, T2, T3, T4> Create<T1, T2, T3, T4>(T1 t1, T2 t2, T3 t3, T4 t4)
    {
        return new TypedTuple<T1, T2, T3, T4>(t1, t2, t3, t4);
    }

    public static TypedTuple<T1, T2, T3, T4, T5> Create<T1, T2, T3, T4, T5>(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5)
    {
        return new TypedTuple<T1, T2, T3, T4, T5>(t1, t2, t3, t4, t5);
    }

    public static TypedTuple<T1, T2, T3, T4, T5, T6> Create<T1, T2, T3, T4, T5, T6>(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6)
    {
        return new TypedTuple<T1, T2, T3, T4, T5, T6>(t1, t2, t3, t4, t5, t6);
    }

    public static TypedTuple<T1, T2, T3, T4, T5, T6, T7> Create<T1, T2, T3, T4, T5, T6, T7>(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7)
    {
        return new TypedTuple<T1, T2, T3, T4, T5, T6, T7>(t1, t2, t3, t4, t5, t6, t7);
    }

    public static TypedTuple<T1, T2, T3, T4, T5, T6, T7, T8> Create<T1, T2, T3, T4, T5, T6, T7, T8>(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8)
    {
        return new TypedTuple<T1, T2, T3, T4, T5, T6, T7, T8>(t1, t2, t3, t4, t5, t6, t7, t8);
    }

}

public class TypedTuple<T>
{
    protected Dictionary<Type, object> items = new Dictionary<Type, object>();

    public TypedTuple(T item1)
    {
        Item1 = item1;
    }

    public TSource Get<TSource>()
    {
        object value;
        if (this.items.TryGetValue(typeof(TSource), out value))
        {
            return (TSource)value;
        }
        else
            return default(TSource);
    }

    private T item1;
    public T Item1 { get { return this.item1; } set { this.item1 = value; this.items[typeof(T)] = value; } }
}

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

    private T2 item2;
    public T2 Item2 { get { return this.item2; } set { this.item2 = value; this.items[typeof(T2)] = value; } }
}

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

    private T3 item3;
    public T3 Item3 { get { return this.item3; } set { this.item3 = value; this.items[typeof(T3)] = value; } }
}

public class TypedTuple<T1, T2, T3, T4> : TypedTuple<T1, T2, T3>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3, T4 item4)
        : base(item1, item2, item3)
    {
        Item4 = item4;
    }

    private T4 item4;
    public T4 Item4 { get { return this.item4; } set { this.item4 = value; this.items[typeof(T4)] = value; } }
}

public class TypedTuple<T1, T2, T3, T4, T5> : TypedTuple<T1, T2, T3, T4>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5)
        : base(item1, item2, item3, item4)
    {
        Item5 = item5;
    }

    private T5 item5;
    public T5 Item5 { get { return this.item5; } set { this.item5 = value; this.items[typeof(T5)] = value; } }
}

public class TypedTuple<T1, T2, T3, T4, T5, T6> : TypedTuple<T1, T2, T3, T4, T5>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6)
        : base(item1, item2, item3, item4, item5)
    {
        Item6 = item6;
    }

    private T6 item6;
    public T6 Item6 { get { return this.item6; } set { this.item6 = value; this.items[typeof(T6)] = value; } }
}

public class TypedTuple<T1, T2, T3, T4, T5, T6, T7> : TypedTuple<T1, T2, T3, T4, T5, T6>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7)
        : base(item1, item2, item3, item4, item5, item6)
    {
        Item7 = item7;
    }

    private T7 item7;
    public T7 Item7 { get { return this.item7; } set { this.item7 = value; this.items[typeof(T7)] = value; } }
}

public class TypedTuple<T1, T2, T3, T4, T5, T6, T7, T8> : TypedTuple<T1, T2, T3, T4, T5, T6, T7>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8)
        : base(item1, item2, item3, item4, item5, item6, item7)
    {
        Item8 = item8;
    }

    private T8 item8;
    public T8 Item8 { get { return this.item8; } set { this.item8 = value; this.items[typeof(T8)] = value; } }
}

4
这似乎是一项艰巨的工作,几乎没有回报。它有一个不直观的局限性(没有重复的类型),我发现仅凭其类型来检索值的想法就非常不直观,并且无法考虑其实际用例。这相当于为员工创建一个数据表,然后决定按其姓氏(而不是唯一键)来检索员工,然后要求所有员工使用不同的名字。这不是解决问题的方法,它是使用解决方案,但会产生额外的问题。
平坦

愿上帝怜悯你的灵魂。
Jamie M.19年

1

这非常烦人,我希望将来的C#版本能够满足这一需求。我发现最简单的解决方法是使用其他数据结构类型或为您的健全性以及其他阅读您的代码的健全性重命名“项目”。

Tuple<ApiResource, JSendResponseStatus> result = await SendApiRequest();
ApiResource apiResource = result.Item1;
JSendResponseStatus jSendStatus = result.Item2;

0

我想我会创建一个类,但另一个选择是输出参数。

public void GetOrderRelatedIds(out int OrderGroupId, out int OrderTypeId, out int OrderSubTypeId, out int OrderRequirementId)

由于您的元组只包含整数,因此可以用 Dictionary<string,int>

var orderIds = new Dictionary<string, int> {
    {"OrderGroupId", 1},
    {"OrderTypeId", 2},
    {"OrderSubTypeId", 3},
    {"OrderRequirementId", 4}.
};

但我也不建议这样做。


0

为什么每个人都让生活如此艰难。元组用于相当临时的数据处理。一直使用Tuples会使代码在某些时候很难理解。为所有内容创建类最终可能会使您的项目肿。

这是关于平衡的,但是...

您的问题似乎是您想要上课的。为了完整起见,下面的此类还包含构造函数。


这是正确的模式

  • 自定义数据类型
    • 没有其他功能。还可以使用代码扩展getter和setter,使用“ _orderGroupId”的名称模式获取/设置私有成员,同时还执行功能代码。
  • 包括构造函数。如果所有属性都是必需的,则还可以选择仅包含一个构造函数。
  • 如果要使用所有构造函数,则像这样冒泡是避免重复代码的正确模式。

public class OrderRelatedIds
{
    public int OrderGroupId { get; set; }
    public int OrderTypeId { get; set; }
    public int OrderSubTypeId { get; set; }
    public int OrderRequirementId { get; set; }

    public OrderRelatedIds()
    {
    }
    public OrderRelatedIds(int orderGroupId)
        : this()
    {
        OrderGroupId = orderGroupId;
    }
    public OrderRelatedIds(int orderGroupId, int orderTypeId)
        : this(orderGroupId)
    {
        OrderTypeId = orderTypeId;
    }
    public OrderRelatedIds(int orderGroupId, int orderTypeId, int orderSubTypeId)
        : this(orderGroupId, orderTypeId)
    {
        OrderSubTypeId = orderSubTypeId;
    }
    public OrderRelatedIds(int orderGroupId, int orderTypeId, int orderSubTypeId, int orderRequirementId)
        : this(orderGroupId, orderTypeId, orderSubTypeId)
    {
        OrderRequirementId = orderRequirementId;
    }
}

或者,如果您希望它真的很简单:您也可以使用类型初始值设定项:

OrderRelatedIds orders = new OrderRelatedIds
{
    OrderGroupId = 1,
    OrderTypeId = 2,
    OrderSubTypeId = 3,
    OrderRequirementId = 4
};

public class OrderRelatedIds
{
    public int OrderGroupId;
    public int OrderTypeId;
    public int OrderSubTypeId;
    public int OrderRequirementId;
}

0

我将用summay写下项目名称。.因此,将鼠标悬停在函数helloworld()上,文本将显示hello = Item1和world = Item2

 helloworld("Hi1,Hi2");

/// <summary>
/// Return hello = Item1 and world Item2
/// </summary>
/// <param name="input">string to split</param>
/// <returns></returns>
private static Tuple<bool, bool> helloworld(string input)
{
    bool hello = false;
    bool world = false;
    foreach (var hw in input.Split(','))
    {
        switch (hw)
        {
            case "Hi1":
                hello= true;
                break;
            case "Hi2":
                world= true;
                break;
        }

    }
    return new Tuple<bool, bool>(hello, world);
}

0

只是添加到@MichaelMocko答案。元组目前有一些陷阱:

您不能在EF表达式树中使用它们

例:

public static (string name, string surname) GetPersonName(this PersonContext ctx, int id)
{
    return ctx.Persons
        .Where(person => person.Id == id)
        // Selecting as Tuple
        .Select(person => (person.Name, person.Surname))
        .First();
}

这将因“表达式树可能不包含元组文字”错误而无法编译。不幸的是,当将表达式树API添加到语言中时,并没有扩展其对元组的支持。

跟踪(并投票)此问题以进行更新:https : //github.com/dotnet/roslyn/issues/12897

要解决此问题,可以先将其强制转换为匿名类型,然后将值转换为元组:

// Will work
public static (string name, string surname) GetPersonName(this PersonContext ctx, int id)
{
    return ctx.Persons
        .Where(person => person.Id == id)
        .Select(person => new { person.Name, person.Surname })
        .ToList()
        .Select(person => (person.Name, person.Surname))
        .First();
}

另一个选择是使用ValueTuple.Create:

// Will work
public static (string name, string surname) GetPersonName(this PersonContext ctx, int id)
{
    return ctx.Persons
        .Where(person => person.Id == id)
        .Select(person => ValueTuple.Create(person.Name, person.Surname))
        .First();
}

参考文献:

您无法在Lambda中解构它们

有一个建议添加支持:https : //github.com/dotnet/csharplang/issues/258

例:

public static IQueryable<(string name, string surname)> GetPersonName(this PersonContext ctx, int id)
{
    return ctx.Persons
        .Where(person => person.Id == id)
        .Select(person => ValueTuple.Create(person.Name, person.Surname));
}

// This won't work
ctx.GetPersonName(id).Select((name, surname) => { return name + surname; })

// But this will
ctx.GetPersonName(id).Select(t => { return t.name + t.surname; })

参考文献:

他们不会很好地序列化

using System;
using Newtonsoft.Json;

public class Program
{
    public static void Main() {
        var me = (age: 21, favoriteFood: "Custard");
        string json = JsonConvert.SerializeObject(me);

        // Will output {"Item1":21,"Item2":"Custard"}
        Console.WriteLine(json); 
    }
}

元组字段名称仅在编译时可用,并在运行时完全清除。

参考文献:


-1

您可以编写一个包含元组的类。

您需要重写Equals和GetHashCode函数

以及==和!=运算符。

class Program
{
    public class MyTuple
    {
        private Tuple<int, int> t;

        public MyTuple(int a, int b)
        {
            t = new Tuple<int, int>(a, b);
        }

        public int A
        {
            get
            {
                return t.Item1;
            }
        }

        public int B
        {
            get
            {
                return t.Item2;
            }
        }

        public override bool Equals(object obj)
        {
            return t.Equals(((MyTuple)obj).t);
        }

        public override int GetHashCode()
        {
            return t.GetHashCode();
        }

        public static bool operator ==(MyTuple m1, MyTuple m2)
        {
            return m1.Equals(m2);
        }

        public static bool operator !=(MyTuple m1, MyTuple m2)
        {
            return !m1.Equals(m2);
        }
    }

    static void Main(string[] args)
    {
        var v1 = new MyTuple(1, 2);
        var v2 = new MyTuple(1, 2);

        Console.WriteLine(v1 == v2);

        Dictionary<MyTuple, int> d = new Dictionary<MyTuple, int>();
        d.Add(v1, 1);

        Console.WriteLine(d.ContainsKey(v2));
    }
}

将返回:

真正

真正


2
如果您已经为此数据类型实现了一个类,为什么要为基础数据而不是属性声明一个元组?
bytecode77

我想使用在Equals函数中按值编译的元组属性
SS

那可能是一个好处。但是另一方面,您基本上创建了一个类,其属性范围从Item1到ItemX。我会使用元组在Equals()中选择适当的命名和更多代码。
bytecode77

-1

C#7元组示例

var tuple = TupleExample(key, value);

     private (string key1, long value1) ValidateAPIKeyOwnerId(string key, string value)
            {
                return (key, value);
            }
      if (!string.IsNullOrEmpty(tuple.key1) && tuple.value1 > 0)
          {
                    //your code

                }     
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.