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

Jas*_*ebb 166 .net c# tuples application-design

随着.net 4 中的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;
}
Run Code Online (Sandbox Code Playgroud)

相当于:

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

因此,抛开我错过了元组点的可能性,是一个Tuple设计选择不好的例子吗?对我来说,它似乎不那么混乱,但不像自我记录和干净.这意味着对于类型ResultType,后面很清楚类的每个部分意味着什么,但你有额外的代码来维护.随着Tuple<string, int>您将需要查找并弄清每一个Item代表,但你编写和维护更少的代码.

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

Bry*_*tts 162

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

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

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

这个答案也有一些信息.

  • 紧密耦合,还是紧紧地混在一起?;) (34认同)
  • 公共/私人之间的区别非常重要,谢谢你提出来.在你的公共API中返回元组是一个非常糟糕的主意,但在内部可能会紧密耦合(但这是*好的*)它没关系. (17认同)

LBu*_*kin 79

我看到它的方式,一个元组是编写结果类的快捷方式(我相信还有其他用途).

确实存在其他有价值的用途Tuple<> - 其中大多数涉及抽象出具有相似结构的特定类型组的语义,并将它们简单地视为有序的值集.在所有情况下,元组的一个好处是它们可以避免使用公开属性但不公开方法的仅数据类来混淆命名空间.

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

var opponents = new Tuple<Player,Player>( playerBob, playerSam );
Run Code Online (Sandbox Code Playgroud)

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

var pokerHand = Tuple.Create( card1, card2, card3, card4, card5 );
Run Code Online (Sandbox Code Playgroud)

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

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

将强类型Tuple<>实例作为公共类型的公共API的一部分返回很少是个好主意.正如您自己认识到的那样,元组要求所涉及的各方(图书馆作者,图书馆用户)提前就目前使用的元组类型和解释达成一致.创建直观且清晰的API是非常具有挑战性的,Tuple<>仅使用公开的API会模糊API的意图和行为.

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

我的经验法则是: 如果您将从公共界面返回它 - 将其命名为命名类型.

我使用元组的另一个经验法则是: 名称方法参数和类型的localc变量Tuple<>尽可能清楚 - 使名称代表元组元素之间关系的含义.想想我的var opponents = ...榜样.

这是一个真实世界案例的例子,我曾经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 );
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

PS还有另一种(更简单的)方法来解决字典中匿名类型引起的问题,那就是使用var关键字让编译器"推断"你的类型.这是那个版本:

var foundItem = peopleDict.FirstOrDefault().Value;
if( peopleDict.TryGetValue( "Smith", out foundItem ) )
{
   // use foundItem...
}
Run Code Online (Sandbox Code Playgroud)

  • @stakx:是的,我同意.但请记住 - 该示例主要用于说明使用`Tuple <>`**有意义的情况.总有其他选择...... (5认同)
  • 我知道元组仅作为不可变的扑克玩家对象有用. (2认同)
  • +1很棒的答案 - 我喜欢你的两个最初的例子,这些例子实际上略微改变了我 我之前从未见过像元组至少与自定义结构或类型一样合适的例子.然而,你对我的最后一个"现实生活中的例子"似乎不再增加太多价值.前两个例子是完美而简单的.最后一点有点难以理解,你的"PS"使它变得无用,因为你的替代方法更简单并且不需要元组. (2认同)

Mat*_*ted 18

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


Dan*_*ker 9

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

但是如果你有一个恒定长度的集合,这意味着集合中固定的位置每个都有一个特定的预定义.而且它总是不如给他们适当的静态的名字在这种情况下,而不是记住的意义Item1,Item2等等.

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @JonofAllTrades - 意见可能会有所不同,但我尝试设计公共方法*好像*他们将被其他人使用,即使有人是我,所以我给自己提供了良好的客户服务!您倾向于调用函数的次数比编写函数多.:) (2认同)

joh*_*y g 8

与关键字类似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>> ();
}
Run Code Online (Sandbox Code Playgroud)

  • 我仍然站在那个var不能被滥用的时期.它只是一个关键字,用于制作更短的变量定义.你的论点也许是关于匿名类型,但即使是那些真的也不能被滥用,因为它们仍然是正常的静态链接类型,现在是动态的是一个完全不同的故事. (5认同)
  • var关键字无法滥用.也许你的意思是动态? (4认同)
  • var可以被滥用,因为如果它被过度使用,它有时会给阅读代码的人带来问题.特别是如果你不在Visual Studio中并缺乏智能感知. (4认同)
  • 主流"RAD"语言(Java是最好的例子)总是选择安全性并避免在脚型场景中拍摄自己.我很高兴微软正在利用C#获得一些机会并给予语言一些不错的力量,即使它可能被滥用. (2认同)

Ric*_*arn 5

使用类ResultType更清晰.你可以给有意义的名称,以在类中的字段(而用一个元组,他们将被称为Item1Item2).如果两个字段的类型相同,则更为重要:名称清楚地区分它们.


Cli*_*rce 5

如何在装饰 - 排序 - 未装饰模式中使用元组?(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();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 您可以使用Tuple.Create而不是构造函数.它更短. (3认同)