首页 文章

在我的C#代码中使用.NET 4.0元组是一个糟糕的设计决策吗?

提问于
浏览
164

通过在.net 4中添加Tuple类,我一直试图决定在我的设计中使用它们是不是一个糟糕的选择 . 我看到它的方式,Tuple可以是编写结果类的快捷方式(我相信还有其他用途) .

所以这:

public class ResultType
{
    public string StringValue { get; set; }
    public int IntValue { get; set; }
}

public ResultType GetAClassedValue()
{
    //..Do Some Stuff
    ResultType result = new ResultType { StringValue = "A String", IntValue = 2 };
    return result;
}

相当于:

public Tuple<string, int> GetATupledValue()
{
    //...Do Some stuff
    Tuple<string, int> result = new Tuple<string, int>("A String", 2);
    return result;
}

那么撇开我错过了元组点的可能性的例子是Tuple一个糟糕的设计选择?对我而言似乎不那么杂乱,但不像自我记录和干净 . 这意味着对于 ResultType 类型,后面很清楚类的每个部分意味着什么,但是你需要额外的代码来维护 . 使用 Tuple<string, int> ,您需要查找并找出每个 Item 代表的内容,但您编写并维护较少的代码 .

您对此选择的任何经验将不胜感激 .

13 回答

  • 9

    元组可能很有用......但是它们之后也会很痛苦 . 如果你有一个返回 Tuple<int,string,string,int> 的方法,你怎么知道这些值以后是什么 . 他们是 ID, FirstName, LastName, Age 还是他们 UnitNumber, Street, City, ZipCode .

  • 0

    不要评判我,我不是专家,但现在使用C#7.x中的新元组,你可以返回类似的东西:

    return (string Name1, int Name2)
    

    至少现在你可以命名它,开发人员可能会看到一些信息 .

  • 161

    从C#程序员的角度来看,元组是CLR的不可忽视的补充 . 如果您有一组长度不同的项目,则在编译时不需要它们具有唯一的静态名称 .

    但是如果你有一个恒定长度的集合,这意味着集合中固定的位置每个都有一个特定的预定义 . 在这种情况下,最好给它们适当的静态名称,而不是必须记住 Item1Item2 等的重要性 .

    C#中的匿名类已经为最常见的私有元组使用提供了极好的解决方案,并且它们为项目提供了有意义的名称,因此它们在这个意义上实际上是优越的 . 唯一的问题是它们不能泄漏命名方法 . 我希望看到限制被解除(可能只针对私有方法),而不是对C#中元组的特定支持:

    private var GetDesserts()
    {
        return _icecreams.Select(
            i => new { icecream = i, topping = new Topping(i) }
        );
    }
    
    public void Eat()
    {
        foreach (var dessert in GetDesserts())
        {
            dessert.icecream.AddTopping(dessert.topping);
            dessert.Eat();
        }
    }
    
  • 4

    元组在.NET 4中是一个无用的框架功能 . 我认为C#4.0错过了一个很好的机会 . 我本来希望有命名成员的元组,所以你可以按名称而不是 Value1Value2 等访问元组的各个字段...

    它本来需要一种语言(语法)改变,但它会非常有用 .

  • 1

    使用像 ResultType 这样的类更清楚 . 您可以为类中的字段指定有意义的名称(而对于元组,它们将被称为 Item1Item2 ) . 如果两个字段的类型相同,则更为重要:名称清楚地区分它们 .

  • 2

    如果你控制创建和使用它们,元组是很棒的 - 你可以保持上下文,这对于理解它们是必不可少的 .

    但是,在公共API上,它们效率较低 . 消费者(不是你)必须猜测或查找文档,特别是像 Tuple<int, int> 这样的东西 .

    我会将它们用于私有/内部成员,但是对公共/受保护成员使用结果类 .

    This answer也有一些信息 .

  • 1

    与关键字 var 类似,它旨在方便 - 但容易被滥用 .

    在我最谦虚的意见中,不要将 Tuple 暴露为返回类 . 私有地使用它,如果服务或组件的数据结构需要它,但从公共方法返回格式良好的知名类 .

    // one possible use of tuple within a private context. would never
    // return an opaque non-descript instance as a result, but useful
    // when scope is known [ie private] and implementation intimacy is
    // expected
    public class WorkflowHost
    {
        // a map of uri's to a workflow service definition 
        // and workflow service instance. By convention, first
        // element of tuple is definition, second element is
        // instance
        private Dictionary<Uri, Tuple<WorkflowService, WorkflowServiceHost>> _map = 
            new Dictionary<Uri, Tuple<WorkflowService, WorkflowServiceHost>> ();
    }
    
  • 8

    我看到它的方式,Tuple是编写结果类的快捷方式(我确信还有其他用途) .

    There are indeed other valuable uses for Tuple<> - 其中大多数涉及抽象出具有相似结构的特定类型组的语义,并将它们简单地视为有序的值集 . 在所有情况下,元组的一个好处是它们可以避免使用仅显示属性但不暴露方法的仅数据类来混淆命名空间 .

    以下是 Tuple<> 合理使用的示例:

    var opponents = new Tuple<Player,Player>( playerBob, playerSam );
    

    在上面的例子中,我们想要代表一对对手,元组是一种方便的方式来配对这些实例而无需创建新类 . 这是另一个例子:

    var pokerHand = Tuple.Create( card1, card2, card3, card4, card5 );
    

    扑克牌可以被认为只是一组牌 - 而元组(可能是)表达这一概念的合理方式 .

    撇开我错过了元组点的可能性,是一个Tuple设计选择不好的例子吗?

    Returning strongly typed Tuple<> instances as part of a public API for a public type is rarely a good idea. 正如您自己所认识到的,元组要求所涉及的各方(图书馆作者,图书馆用户)提前就目前使用的元组类型和解释达成一致 . 创建直观且清晰的API具有挑战性,公开使用 Tuple<> 只会模糊API的意图和行为 .

    Anonymous types are also a kind of tuple - 但是,它们是强类型的,允许您为属于该类型的属性指定清晰,信息丰富的名称 . 但是匿名类型很难在不同的方法中使用 - 它们主要被添加到LINQ等支持技术中,其中投影会生成我们通常不想分配名称的类型 . (是的,我知道具有相同类型和命名属性的匿名类型由编译器) .

    My rule of thumb is: 如果您将从公共界面返回它 - 将其命名为命名类型 .

    My other rule of thumb for using tuples is: 名称方法参数和类型 Tuple<> 的localc变量尽可能清楚 - 使名称表示元组元素之间关系的含义 . 想想我的 var opponents = ... 例子 .

    这里's an example of a real-world case where I' ve使用 Tuple<> 来避免声明仅数据类型仅在我自己的程序集中使用 . 这种情况涉及这样的事实:当使用包含匿名类型的通用字典时,使用 TryGetValue() 方法查找字典中的项目变得很困难,因为该方法需要 out 参数,该参数无法命名:

    public static class DictionaryExt 
    {
        // helper method that allows compiler to provide type inference
        // when attempting to locate optionally existent items in a dictionary
        public static Tuple<TValue,bool> Find<TKey,TValue>( 
            this IDictionary<TKey,TValue> dict, TKey keyToFind ) 
        {
            TValue foundValue = default(TValue);
            bool wasFound = dict.TryGetValue( keyToFind, out foundValue );
            return Tuple.Create( foundValue, wasFound );
        }
    }
    
    public class Program
    {
        public static void Main()
        {
            var people = new[] { new { LastName = "Smith", FirstName = "Joe" },
                                 new { LastName = "Sanders", FirstName = "Bob" } };
    
            var peopleDict = people.ToDictionary( d => d.LastName );
    
            // ??? foundItem <= what type would you put here?
            // peopleDict.TryGetValue( "Smith", out ??? );
    
            // so instead, we use our Find() extension:
            var result = peopleDict.Find( "Smith" );
            if( result.First )
            {
                Console.WriteLine( result.Second );
            }
        }
    }
    

    P.S. 还有另一种(更简单的)方法来解决字典中匿名类型引起的问题,那就是使用 var 关键字让编译器'infer'为您提供类型 . 这是那个版本:

    var foundItem = peopleDict.FirstOrDefault().Value;
    if( peopleDict.TryGetValue( "Smith", out foundItem ) )
    {
       // use foundItem...
    }
    
  • 5

    我在许多不同的场景中使用了元组,包括 Tuple 和新的 ValueTuple ,并得出以下结论: do not use .

    每次,我都遇到了以下问题:

    由于缺乏强有力的命名,

    • 代码变得难以理解;

    • 无法使用类层次结构功能,例如基类DTO和子类DTO等;

    • 如果它们在多个地方使用,你最终会复制并粘贴这些丑陋的定义,而不是干净的类名 .

    我的观点是元组是C#的一个不利因素,而不是一个特征 .

    我有点相似,但不那么苛刻,批评 Func<>Action<> . 这些在很多情况下很有用,特别是简单的 ActionFunc<type> 变体,但除此之外,我发现创建委托类型更优雅,可读,可维护,并为您提供更多功能,如 ref / out 参数 .

  • 1

    IMO这些"tuples"基本上都是公开访问匿名 struct 类型的未命名成员 .

    我将使用元组的唯一地方是当你需要在非常有限的范围内快速填充一些数据时 . The semantics of the data should be are obvious ,所以代码不难阅读 . 所以使用元组( intint )(row,col)似乎是合理的 . 但我很难找到一个优于 struct 与命名成员的优势(所以没有错误,行/列不会意外地互换)

    如果您要将数据发送回调用者,或者接受来自调用者的数据,那么您确实应该使用带有命名成员的 struct .

    举一个简单的例子:

    struct Color{ float r,g,b,a ; }
    public void setColor( Color color )
    {
    }
    

    元组版本

    public void setColor( Tuple<float,float,float,float> color )
    {
      // why?
    }
    

    我没有看到使用元组代替具有命名成员的结构的任何优点 . 对于代码的可读性和可理解性,使用未命名的成员是一个倒退 .

    Tuple以一种懒惰的方式打击我,以避免使用实际的命名成员创建 struct . 过度使用元组,你真的觉得你/或其他人遇到你的代码需要 named members 是一个坏事™,如果我见过一个 .

  • 5

    当然,这取决于!正如您所说,当您想要将一些项目组合在一起以供本地使用时,元组可以节省您的代码和时间 . 您还可以使用它们来创建比通过具体类更多的通用处理算法 . 我记不清有多少次我希望我有一些超越KeyValuePair或DataRow的东西来快速将一些日期从一种方法传递到另一种方法 .

    另一方面,它很可能过度使用并传递元组,你只能猜测它们包含的内容 . 如果要在类之间使用元组,也许最好创建一个具体的类 .

    当然,在适度使用时,元组可以使代码更简洁和可读 . 您可以查看C,STL和Boost,了解如何在其他语言中使用元组,但最后,我们都必须尝试找出它们如何最适合.NET环境 .

  • 17

    如何在装饰 - 排序 - 未装饰模式中使用元组? (Schwartzian变换为Perl人) . 这是一个人为的例子,但是Tuples似乎是处理这类事情的好方法:

    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                string[] files = Directory.GetFiles("C:\\Windows")
                        .Select(x => new Tuple<string, string>(x, FirstLine(x)))
                        .OrderBy(x => x.Item2)
                        .Select(x => x.Item1).ToArray();
            }
            static string FirstLine(string path)
            {
                using (TextReader tr = new StreamReader(
                            File.Open(path, FileMode.Open)))
                {
                    return tr.ReadLine();
                }
            }
        }
    }
    

    现在,我可以使用两个元素的Object [],或者在这个特定示例中使用两个元素的字符串[] . 关键是我可以使用任何东西作为内部使用的元组中的第二个元素,并且非常容易阅读 .

  • 78

    我个人从不使用元组作为返回类型,因为没有指示值代表什么 . 元组具有一些有 Value 的用途,因为与对象不同,它们是值类型,因此理解相等 . 因为这个我如果我需要多部分键或作为GroupBy子句的键,如果我想按多个变量分组并且不想要嵌套分组(谁想要嵌套分组?),它们将使用它们作为字典键 . 要克服极端冗长的问题,您可以使用辅助方法创建它们 . 请记住,如果您经常访问成员(通过Item1,Item2等),那么您应该使用不同的结构,如结构或匿名类 .

相关问题