在我的C#中使用.NET 4.0元组代码糟糕的devise决策?
随着.net 4中Tuple类的增加,我一直在试图决定在我的devise中使用它们是不是一个不好的select。 我看到它的方式, 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; }
因此,抛开我缺less元组点的可能性,是一个不好的deviseselect的例子吗? 对我来说,似乎不那么混乱,但不是自我logging和清洁。 这意味着对于typesResultType
,后面将很清楚类的每个部分是什么意思,但是您有额外的代码来维护。 使用Tuple<string, int>
您需要查找每个Item
代表的内容,但是您需要编写和维护更less的代码。
任何经验,你有这个select将不胜感激。
如果您控制创build和使用元组,那么元组是非常棒的 – 您可以维护上下文,这对理解它们是非常重要的。
然而,在公共API上,它们效率不高。 消费者(不是你)不得不猜测或查找文档,特别是像Tuple<int, int>
这样的东西。
我会使用它们作为私人/内部成员,但使用公共/受保护成员的结果类。
这个答案也有一些信息。
我看到它的方式,Tuple是编写结果类的捷径(我相信也有其他用途)。
对于Tuple<>
确实还有其他一些有价值的用途 – 其中大多数涉及到将具有相似结构的特定types组的语义抽象出来,并将它们简单地看成是一组有序的值。 在所有情况下,元组的一个好处是它们避免了使用暴露属性而不暴露方法的纯数据类来混淆名字空间。
下面是Tuple<>
合理使用的一个例子:
var opponents = new Tuple<Player,Player>( playerBob, playerSam );
在上面的例子中,我们想要表示一对对手,一个元组是一个方便的方式来配对这些实例,而不必创build一个新的类。 这是另一个例子:
var pokerHand = Tuple.Create( card1, card2, card3, card4, card5 );
扑克牌手可以被认为只是一套牌 – 而元组(可能)是expression该概念的合理方式。
抛开我缺less元组点的可能性,是一个不好的deviseselect的例子吗?
将强types的Tuple<>
实例作为publictypes的公共API的一部分返回,这不是一个好主意。 正如你自己所认识到的,元组要求参与方(图书馆作者,图书馆用户)提前就所使用的元组types的目的和解释达成一致。 创build直观清晰的API足够具有挑战性,公开使用Tuple<>
只会掩盖API的意图和行为。
匿名types也是一种元组 – 但是,它们是强types的,并且允许为属于该types的属性指定清晰的信息名称。 但是匿名types很难在不同的方法中使用 – 它们主要被添加来支持像LINQ这样的技术,其中投影会产生我们通常不想分配名称的types。 (是的,我知道具有相同types和命名属性的匿名types由编译器合并)。
我的经验法则是: 如果你将从你的公共接口返回它 – 使它成为一个命名的types 。
我使用元组的另一个经验法则是: 名称方法参数和types为Tuple<>
localcvariables尽可能清楚 – 使名称代表元组之间关系的含义。 想想我的var opponents = ...
例子。
下面是一个使用Tuple<>
的真实案例的示例,以避免声明只用于我自己的程序集中的数据types。 这种情况涉及到使用包含匿名types的通用字典时,使用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 ); } } }
PS还有另一种(简单的)方式来解决词典中匿名types引起的问题,也就是使用var
关键字让编译器为你“推断”types。 这是这个版本:
var foundItem = peopleDict.FirstOrDefault().Value; if( peopleDict.TryGetValue( "Smith", out foundItem ) ) { // use foundItem... }
元组可以是有用的…但是它们也可能在以后变得痛苦。 如果你有一个返回Tuple<int,string,string,int>
方法,你怎么知道这些值是什么。 是他们的ID, FirstName, LastName, Age
还是他们UnitNumber, Street, City, ZipCode
。
从C#程序员的angular度来看,元组是非常不可思议的。 如果你有一个长度不同的项目集合,你不需要它们在编译时拥有唯一的静态名称。
但是如果你有一个恒定长度的集合,这意味着集合中的固定位置都有一个特定的预定义。 在这种情况下最好给它们适当的静态名称,而不必记住Item1
, Item2
等的意义。
C#中的匿名类已经为元组的最常见的私有用途提供了一个极好的解决scheme,并为这些项赋予了有意义的名称,所以它们在这个意义上实际上是优越的。 唯一的问题是他们不能泄漏命名的方法。 我宁愿看到限制(可能只适用于私有方法),而不是特别支持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(); } }
与关键字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>> (); }
使用像ResultType
这样的类更清晰。 你可以给类中的字段赋予有意义的名字(而使用元组,他们将被称为Item1
和Item2
)。 如果这两个字段的types是相同的,那么这更加重要:名称明确地区分它们。
如何使用Tuple-Sort-undecorate模式的元组? (Perl人Schwartzian变换)。 这是一个人为的例子,当然,元组似乎是处理这种事情的好方法:
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[]。 重要的是,我可以使用任何东西作为内部使用的元组中的第二个元素,很容易阅读。
国际海事组织这些“元组”基本上是所有公共访问匿名struct
types与未命名的成员 。
我会使用元组的唯一的地方是当你需要在一个非常有限的范围内快速地将一些数据混合在一起时。 数据的语义应该是显而易见的 ,所以代码不难阅读。 所以对(row,col)使用一个元组( int
, int
)似乎是合理的。 但是,我很难find一个有名字成员的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作为一种懒惰的方式来打击我,以避免用实际的命名成员创build一个struct
。 过度使用元组,如果你真的觉得你/或者别人遇到你的代码,那么需要命名的成员是A Bad Thing ?,如果我曾经看到过。
这当然取决于! 正如你所说,一个元组可以节省你的代码和时间,当你想把一些项目组合在一起供本地使用时。 如果你传递一个具体的类,你也可以使用它们来创build更通用的处理algorithm。 我不记得有多less次我希望有超越KeyValuePair或DataRow的东西来快速地从一种方法传递一些date到另一种。
另一方面,它很有可能过度使用它,并且只能猜测它们包含的元组。 如果你要跨类使用元组,也许最好创build一个具体的类。
在适当的时候,元组可以导致更简洁和可读的代码。 你可以看看C ++,STL和Boost的例子,说明如何在其他语言中使用元组,但是最终我们将不得不尝试找出它们最适合.NET环境的方式。
元组在.NET 4中是无用的框架function。我认为C#4.0错过了一个很好的机会。 我会喜欢有名称成员的元组,所以你可以通过名称而不是Value1 , Value2等来访问元组的各个字段。
这将需要一个语言(语法)的改变,但它会非常有用。
我个人从来没有使用一个元组作为返回types,因为没有指示什么值代表。 元组有一些有价值的用途,因为不同于对象,它们是价值types,从而理解平等。 正因为如此,如果我想用多个variables进行分组,并且不希望嵌套分组(谁曾经想要嵌套分组?),那么我将使用它们作为字典键,如果我需要多部分键或作为GroupBy子句的键。 为了克服这个问题,你可以用一个辅助方法来创build它们。 请记住,如果你经常访问成员(通过Item1,Item2等),那么你应该使用不同的结构,如结构或匿名类。