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

中添加了 Tuple类。网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;
}

相当于:

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

那么,撇开我没有抓住 Tuples 要点的可能性不谈,使用 ABc3的例子是一个糟糕的设计选择吗?在我看来,它似乎没有那么杂乱,但不是自我记录和干净。这意味着,对于 ResultType类型,以后会非常清楚类的每个部分的含义,但是您需要维护额外的代码。对于 Tuple<string, int>,您需要查找并找出每个 Item代表什么,但是您编写和维护的代码较少。

如果你有这方面的经验,我将不胜感激。

42584 次浏览

使用像 ResultType这样的类更清楚。您可以为类中的字段提供有意义的名称(而对于元组,它们将被称为 Item1Item2)。如果这两个字段的类型是相同的,那么这一点就更加重要了: 名称可以清楚地区分它们。

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

如果您同时控制元组的创建和使用,那么元组是非常棒的——您可以维护上下文,这对于理解它们是必不可少的。

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

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

这个答案 也有一些信息。

当然,要看情况!正如您所说的,当您希望将一些项目组合在一起供本地使用时,元组可以节省代码和时间。您还可以使用它们来创建比传递具体类更通用的处理算法。我已经记不清有多少次我希望拥有 KeyValuePair 或 DataRow 之外的东西来快速地将某个日期从一个方法传递到另一个方法。

另一方面,很有可能做过头,传递元组,而您只能猜测它们包含什么。如果要在类之间使用元组,也许最好创建一个具体的类。

当然,适度地使用元组可以使代码更加简洁和可读。你可以从 C + + 、 STL 和 Boost 中找到元组在其他语言中使用的例子,但是最后,我们都必须尝试找到它们如何最好地适应。NET 环境。

类似于关键字 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>> ();
}

在我看来,Tuple 是编写结果类(I 我相信还有其他用途)。

的确,Tuple<> 还有其他一些有价值的用途——大多数都涉及抽象出共享相似结构的特定类型组的语义,并将它们简单地视为有序的值集合。在所有情况下,元组的一个好处是,它们可以避免只公开属性而不公开方法的数据类使命名空间杂乱无章。

下面是 Tuple<>合理使用的一个例子:

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

在上面的示例中,我们希望表示一对对手,元组是一种方便的方法,可以不必创建新类就可以对这些实例进行配对。下面是另一个例子:

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

扑克牌可以被看作是一组牌——元组(tuple)可能是表达这个概念的一种合理方式。

先不考虑我 我不明白 Tuples 的意义 元组的例子一个糟糕的设计 选择?

将强类型 Tuple<>实例作为公共 API 的一部分返回给公共类型很少是一个好主意。正如您自己所认识到的,元组要求所涉及的各方(库作者、库用户)提前就所使用的元组类型的用途和解释达成一致。创建直观和清晰的 API 已经够具有挑战性的了,公开使用 Tuple<>只会掩盖 API 的意图和行为。

匿名类型也是一种 tuple -但是,它们是强类型的,允许您为属于该类型的属性指定清晰、信息丰富的名称。但是匿名类型很难在不同的方法之间使用——它们主要是为了支持 LINQ 这样的技术而添加的,在这种技术中,投影会产生我们通常不想赋予名称的类型。(是的,我知道具有相同类型和命名属性的匿名类型是由编译器合并的)。

我的经验法则是: 如果您将从您的公共接口返回它-使它成为命名类型

我使用元组的另一个经验法则是: 尽可能清楚地使用 Tuple<>类型的 name 方法参数和 localc 变量——使名称代表元组元素之间关系的含义。想想我的 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 );
}
}
}

P.S. 还有另一种(更简单的)解决字典中匿名类型引起的问题的方法,那就是使用 var关键字让编译器为您“推断”类型。这么说吧:

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

元组是一个无用的框架特性。NET 4.我认为 C # 4.0错过了一个很好的机会。我希望拥有带有命名成员的元组,这样您就可以通过名称而不是 价值1价值2等来访问元组的各个字段。.

它需要改变语言(语法) ,但是它会非常有用。

如何在装饰-排序-非装饰模式中使用元组?(Perl 人的 Schwartzian 变换)。这里有一个人为的例子,可以肯定,但 Tuple 似乎是处理这类事情的一个好方法:

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 [] ,或者在这个特定的示例中使用两个元素的 string []。重点是,我可以使用任何元素作为 Tuple 中的第二个元素,这些元素在内部使用,并且非常容易阅读。

从 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();
}
}

IMO 这些“元组”基本上都是公共访问匿名 struct类型的 还有不知名的成员

我使用 tuple 的 只有地方是当您需要在一个非常有限的范围内快速地将一些数据块放在一起时。数据的语义应该是显而易见的,所以代码不难读。因此,对(row,col)使用元组(intint)似乎是合理的。但是我很难找到一个优于带有命名成员的 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 没有任何好处。使用未命名成员是代码可读性和可理解性的倒退。

对我来说,Tuple 是一种避免创建带有实际命名成员的 struct的懒惰方法。过度使用元组,你真的觉得你/或其他人遇到你的代码会需要 指定成员是一件坏事 TM 如果我曾经看到一个。

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

不要评判我,我不是专家,但是现在 Tuples 在 C # 7. x 中,你可以返回这样的内容:

return (string Name1, int Name2)

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

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

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

  • 由于缺乏强有力的命名,代码变得不可读;
  • 不能使用类层次结构特征,如基类 DTO 和子类 DTO 等;
  • 如果它们在不止一个地方使用,那么您最终将复制和粘贴这些丑陋的定义,而不是一个干净的类名。

我的观点是元组是 C # 的一个缺点,而不是一个特性。

我对 Func<>Action<>有些类似的批评,但是没有那么严厉。这些在许多情况下都很有用,特别是简单的 ActionFunc<type>变体,但是除此之外,我发现创建一个委托类型更优雅、可读、可维护,并且提供更多的特性,比如 ref/out参数。