【问题标题】:Better naming in Tuple classes than "Item1", "Item2"元组类中的命名比“Item1”、“Item2”更好
【发布时间】:2011-12-06 10:51:32
【问题描述】:

有没有办法使用 Tuple 类,但提供其中项目的名称?

例如:

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

返回 OrderGroupId、OrderTypeId、OrderSubTypeId 和 OrderRequirementId 的 ID。

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

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

【问题讨论】:

标签: c# c#-4.0 tuples


【解决方案1】:

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

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

【讨论】:

  • 语法是List&lt;(int first, int second)&gt;。我必须从 NuGet 下载 System.ValueTuple 包才能让它在 Visual Studio 2017 中工作。
  • 创造价值return (first: first, middle: middle, last: last);
  • 或仅:return (first, middle, last); in .NET 4.7.1(不确定 4.7.0)
  • 为了使用它,您需要添加 System.ValueTuple nuget 包
  • 需要注意的是,C# 7 的ValueTuple 虽然通常很棒,但它是一个可变值类型(结构),而Tuple 是一个不可变引用类型(类)。据我所知,没有办法获得带有友好项目名称的引用类型Tuple
【解决方案2】:

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

【讨论】:

  • 我不敢相信这个答案以 40 分被接受。你至少可以展示一个具有适当构造函数的类如何替代它。
  • @bytecode77 好吧,很快这个答案就会直接出错:github.com/dotnet/roslyn/issues/347
  • 我看过这些语言活动提案。但直到现在,对于更复杂的数据类型,类是唯一合适的解决方案。无论如何,您为什么要强制使用元组(请参阅其他答案)
  • C# 7 发布后,可以这样做:msdn.microsoft.com/en-us/magazine/mt595758.aspx
  • Q' 有 c#4 作为标签,所以虽然这个答案很短但仍然是正确的。
【解决方案3】:

这是您所问问题的一个过于复杂的版本:

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; } }

}

为什么不直接上课呢?

【讨论】:

  • 在这种情况下 struct 会比 Class 更好吗?
  • 我看到的一点好处是它自动实现了equals运算符,如果项目都相等,则检查2个实例是否相等。
  • 这种方法的另一个缺点是 Item1 和 Item2 仍然是 MyTuple 上的公共属性
  • @deathrace 元组本身就是类,所以如果你想直接从Tuple&lt;T, T2&gt; 继承你就不能是结构体。
  • 我可能是错的,但我主要使用元组来返回一个对象但不想定义一个特定的类..
【解决方案4】:

this 帖子中复制我的答案,因为它更适合这里。

从 C# v7.0 开始,现在可以将之前默认的元组属性命名为 Item1Item2 等名称。

命名元组文字的属性

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

控制台输出:

Name - Foo, Age - 22, Passion - Bar

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

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 ("Foo", "Bar", "Foo-PC", 1000);
}

控制台输出:

Employee Details: Foo, Bar, Foo-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 - cow  
5 - chickens  
1 - airplane

注意:本文中的代码 sn-ps 使用 C# v6 的字符串插值功能,详见here

【讨论】:

    【解决方案5】:

    使用 .net 4,您也许可以查看 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.
    

    还有一点值得一提的是anonymous type在方法内,但是如果你想返回它,你需要创建一个类。

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

    【讨论】:

      【解决方案6】:

      MichaelMocko 的回答很棒,

      但我想补充一些我必须弄清楚的事情

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

      如果您使用的是 .net 框架

      上面的行会给您编译时错误

      因此,如果您有一个使用 .net 框架 的项目,但仍想使用 ValueTuple 而不是 workAround 将安装 this NuGet 包

      更新:

      从方法返回命名元组并使用它的示例

      public static (string extension, string fileName) GetFile()
      {
          return ("png", "test");
      }
      

      使用它

      var (extension, fileName) = GetFile();
      
      Console.WriteLine(extension);
      Console.WriteLine(fileName);
      

      【讨论】:

        【解决方案7】:

        到今天为止,就是这么简单。而不是使用元组关键字

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

        使用这个。

        public (int alpha, int beta, int candor) GetOrderRelatedIds()
        

        获取这样的值。

        var a = GetOrderRelatedIds();
        var c = a.alpha;
        

        【讨论】:

          【解决方案8】:

          不,您不能命名元组成员。

          中间是使用ExpandoObject 而不是元组。

          【讨论】:

            【解决方案9】:

            如果你的物品的类型都不同,这里是我做的一个类,让它们更直观。

            这个类的用法:

            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; } }
            }
            

            【讨论】:

            • 这似乎需要做很多工作,但收效甚微。它有一个不直观的限制(没有重复的类型),我发现仅通过其类型检索值的想法非常不直观,并且无法想到它的实际用例。这相当于为员工创建一个数据表,然后决定按他们的名字(而不是唯一键)检索员工,然后要求所有员工有不同的名字。这不是解决问题的方法,而是以产生额外问题为代价使用解决方案。
            • 愿上帝怜悯你的灵魂。
            【解决方案10】:

            这很烦人,我希望 C# 的未来版本能够满足这一需求。我发现最简单的解决方法是使用不同的数据结构类型或重命名“项目”以确保您的理智和阅读您代码的其他人的理智。

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

            【讨论】:

              【解决方案11】:

              只是添加到@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();
              }
              
              

              参考资料:

              你不能在 lambdas 中解构它们

              有建议添加支持: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); 
                  }
              }
              

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

              参考资料:

              【讨论】:

                【解决方案12】:
                (double, int) t1 = (4.5, 3);
                Console.WriteLine($"Tuple with elements {t1.Item1} and {t1.Item2}.");
                // Output:
                // Tuple with elements 4.5 and 3.
                
                (double Sum, int Count) t2 = (4.5, 3);
                Console.WriteLine($"Sum of {t2.Count} elements is {t2.Sum}.");
                // Output:
                // Sum of 3 elements is 4.5.
                

                来自文档:https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-tuples

                【讨论】:

                  【解决方案13】:

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

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

                  由于您的元组仅包含整数,您可以用 Dictionary&lt;string,int&gt; 表示它

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

                  但我也不建议这样做。

                  【讨论】:

                    【解决方案14】:

                    为什么每个人都让生活变得如此艰难。元组用于临时数据处理。一直使用元组会使代码在某些时候非常难以理解。为一切创建类最终可能会使您的项目膨胀。

                    这关乎平衡,然而……

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


                    这是正确的模式

                    • 自定义数据类型
                      • 没有其他功能。 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;
                    }
                    

                    【讨论】:

                      【解决方案15】:

                      我会在摘要中写下项目名称.. 因此,通过将鼠标悬停在函数 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);
                      }
                      

                      【讨论】:

                        【解决方案16】:

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

                        您需要重写 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));
                            }
                        }
                        

                        将返回:

                        是的

                        是的

                        【讨论】:

                        • 如果你已经为这种数据类型实现了一个类,为什么你要为底层数据声明一个元组而不只是属性?
                        • 我想在 Equals 函数中使用它按值比较的元组属性
                        • 这可能是一个奖励。但另一方面,您基本上创建了一个属性范围从 Item1 到 ItemX 的类。我会在 Equals() 中选择正确的命名和更多代码而不是使用元组。
                        【解决方案17】:

                        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
                        
                                        }     
                        

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 1970-01-01
                          • 2012-08-05
                          • 1970-01-01
                          • 2014-08-28
                          • 2020-03-06
                          • 1970-01-01
                          • 2020-05-30
                          • 2013-12-29
                          相关资源
                          最近更新 更多