C#コードで.NET 4.0タプルを使用するのは設計上の決定が不十分ですか?


166

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

これと同等です:

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

それで、タプルのポイントを逃している可能性を別にして、タプルの例は悪い設計の選択ですか?私にはすっきりしているように見えますが、自己文書化していてクリーンではありません。つまり、type ResultTypeを使用すると、クラスの各部分の意味が後で非常に明確になりますが、維持する追加のコードがあります。を使用するTuple<string, int>Item、それぞれが何を表しているのかを調べて理解する必要がありますが、作成して保守するコードが少なくなります。

この選択での経験は大歓迎です。


16
@Boltbait:タプルは集合論en.wikipedia.org/wiki/Tuple
Matthew Whited

16
@BoltBait:「タプル」は順序付けられた値のセットであり、必ずしもデータベースの行とは関係ありません。
FrustratedWithFormsDesigner

19
c#のタプルは、いくつかの優れたタプルアンパックアクションがあった場合、さらに優れたものになります:)
Justin

4
@John Saunders私の質問は特にC#での設計決定についてです。私は他の.net言語を使用していないので、タプルがそれらにどのように影響するかはわかりません。だから私はそれをc#とマークしました。それは私には理にかなっているように思われましたが、.netへの変更も私が推測するので結構です。
Jason Webb

13
@John Saunders:純粋にC#で記述されたアプリケーションでタプルを使用するかどうかに関係する設計上の決定について、彼が尋ねていると確信しています。彼がC#言語を作成する際に行った設計上の決定について尋ねているとは思いません。
Brett Widmeier、2010年

回答:


163

タプルは、作成と使用の両方を制御する場合に最適です。タプルを理解するために不可欠なコンテキストを維持できます。

ただし、パブリックAPIでは、効果が低くなります。消費者(あなたではありません)は、特にのようなものについては、ドキュメントを推測するか調べる必要がありますTuple<int, int>

私はそれらをプライベート/内部メンバーに使用しますが、パブリック/保護されたメンバーには結果クラスを使用します。

この回答にもいくつかの情報があります。


19
公開してくれてありがとう、パブリック/プライベートの違いは本当に重要です。パブリックAPIでタプルを返すことは非常に悪い考えですが、内部的には物事が密に結合されている可能性がありますが(それは問題ありません)、問題ありません。
クリントンピアス

36
密結合ですか、それとも密結合ですか?;)
contactmatt 2013年

ここでは適切なドキュメントが役に立ちませんか?たとえば、Scala StringOps(基本的にはJavaの「拡張メソッド」のクラスString)にはメソッドがありますparition(Char => Boolean): (String, String)(述語を受け取り、2つの文字列のタプルを返します)。出力が何であるかは明らかです(明らかに、1つは述語が真であった文字であり、もう1つは偽であった文字ですが、どちらがどれであるかは明らかではありません)。これを明確にするのはドキュメントです(そして、パターンマッチングを使用して、どちらを使用するかを明確にすることができます)。
2015年

@マイク:それは正しく理解するのを難しくし、間違いを起こしやすくします、誰かのどこかで痛みを引き起こすAPIの特徴:-)
Bryan Watts

79

私の見たところ、タプルは結果クラスを書くための近道です(他の用途もあると思います)。

確かに他にも価値のある用途があります。Tuple<>それらのほとんどは、類似した構造を共有する特定のタイプのグループのセマンティクスを抽象化し、それらを単に順序付けられた値のセットとして扱うことを含みます。すべての場合において、タプルの利点は、プロパティを公開するがメソッドは公開しないデータのみのクラスで名前空間を混乱させないことです。

の適切な使用例を次に示しますTuple<>

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

上記の例では、対となる対を表現する必要があります。タプルは、新しいクラスを作成せずに、これらのインスタンスを対にする便利な方法です。次に別の例を示します。

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

ポーカーハンドは、単なるカードのセットと考えることができます-そしてタプル(多分)は、その概念を表現するための合理的な方法です。

タプルのポイントを逃している可能性を別にして、タプルの例は設計上の選択として不適切ですか?

強く型付けされたTuple<>インスタンスをパブリック型のパブリックAPIの一部として返すことは、めったにありません。ご存じのように、タプルは、関係する当事者(ライブラリー作成者、ライブラリー・ユーザー)が、使用されるタプル・タイプの目的と解釈につ​​いて事前に合意する必要があります。直感的で明確なAPIを作成することは十分に挑戦的であり、Tuple<>公的に使用することは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 );
        }
    }
}

PS辞書の匿名型から生じる問題を回避する別の(より簡単な)方法があります。それは、varキーワードを使用してコンパイラーに型を「推測」させることです。これがそのバージョンです。

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

5
@stakx:はい、同意します。しかし、心に留めておく-の例は、主の使用が時にケースを例示することを意図しているTuple<> 可能性が意味をなします。常に代替案があります...
LBushkin

1
TryGetValueやTryParseのように、出力パラメーターをタプルで置き換えることは、パブリックAPIがタプルを返すことが理にかなっている数少ないケースの1つだと思います。実際、少なくとも1つの.NET言語がこのAPIを自動的に変更します。
Joel Mueller

1
@stakx:タプルも不変ですが、配列は不変です。
Robert Paulson、2010年

2
タプルは不変のポーカープレーヤーオブジェクトとしてのみ役立つことを理解しました。
Gennady VaninГеннадийВанин

2
+1すばらしい答え-実際に少し気が変わった最初の2つの例が大好きです。タプルが少なくともカスタムの構造体や型と同じくらい適切であった例を今まで見たことがない。しかし、私にとってあなたの最後の「実生活の例」は、もはやあまり価値を加えていないようです。最初の2つの例は完璧でシンプルです。最後の方法は理解するのが少し難しく、 "PS"の種類はそれを役に立たなくします。代替のアプローチははるかに単純で、タプルを必要としないためです。
chiccodoro 2014年

18

タプルは便利かもしれませんが、後で苦痛になることもあります。Tuple<int,string,string,int>どのようにそれらを返すかを返すメソッドがある場合、それらの値は後でわかります。彼らだったID, FirstName, LastName, Ageか、彼らだったかUnitNumber, Street, City, ZipCode


9

タプルは、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();
    }
}

一般的な機能として、私が見たいのは、.netがいくつかの特別な種類の匿名タイプの標準を定義することです。たとえばstruct var SimpleStruct {int x, int y;}、単純な構造体に関連付けられたguidで始まる名前で構造体を定義し、{System.Int16 x}{System.Int16 y}追加されたようなものがあります。そのGUIDで始まる名前は、それらの要素と特定の指定されたコンテンツのみを含む構造体を表すために必要です。同じ名前の複数の定義がスコープ内にあり、それらが一致する場合、すべて同じタイプと見なされます。
スーパーキャット2013

このようなことは、一致する構造が一致するセマンティクスを意味すると見なされるべき状況で使用するためのインターフェースとデリゲートだけでなく、可変および不変のクラスおよび構造体タプルを含む、いくつかの種類のタイプに役立ちます。同じパラメーターを持つ2つのデリゲート型を交換可能であると見なさない方が便利な理由は確かにありますが、メソッドがパラメーターの組み合わせを取るデリゲートを必要とするが、それを超えるデリゲートが必要であることを指定できる場合にも役立ちます。それがどのようなデリゲートであるかは気にしません。
スーパーキャット2013

残念なことに、2つの異なる人々が単一のrefパラメーターを必要とする入力デリゲートとして受け取る関数を記述したい場合、両方の関数に渡すことができる1つのデリゲートを持つことができる唯一の方法は、1人が生成してコンパイルする場合です。デリゲート型を作成し、相手にそれを構築して渡します。2人のタイプが同義であると見なされる必要があることを両方の人々が示すことができる方法はありません。
スーパーキャット2013

匿名クラスをどこで使用するのか、より具体的な例はありますTupleか?私はタプルを使用しているコードベースの50か所をざっと目を通しました。すべてが戻り値(通常はyield return)であるか、他の場所で使用されるコレクションの要素であるため、匿名クラスは必要ありません。

2
@JonofAllTrades-意見はおそらく異なりますが、誰かが私であっても、それらが他の誰かに使用されるようにパブリックメソッドを設計しようとするので、私は自分に良いカスタマーサービスを提供しています!関数を記述するよりも、関数を呼び出す回数が多い傾向があります。:)
Daniel Earwicker 2013

8

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

2
主流の「RAD」言語(Javaが最良の例です)は常に安全を選択し、足のタイプのシナリオで自分を撃つことを避けています。MicrosoftがC#でいくつかのチャンスを利用し、悪用される可能性があるとしても、言語に素晴らしい力を与えていることをうれしく思います。
Matt Greer

4
varキーワードを悪用することはできません。多分あなたは動的を意味しましたか?
Chris Marisic

@Chris Marisic、私が同じシナリオで意味しなかったことを明確にするために-あなたは絶対に正しいです、var返されたり、宣言されたスコープの外に存在したりすることはできません。ただし、意図した目的を超えて使用され、「悪用」される可能性があります
ジョニーg

5
私はまだvarを乱用することはできないということを理解しています。変数の定義を短くするための単なるキーワードです。あなたの議論はおそらく匿名型についてですが、それらはまだ通常の静的リンク型であるため、実際に悪用されることはありません。
Chris Marisic 2010年

4
varは、過度に使用するとコードを読む人に問題を引き起こす可能性があるという意味で悪用される可能性があります。特に、Visual Studioを使用しておらず、インテリセンスが不足している場合はなおさらです。
Matt Greer

5

のようなクラスを使用すると、ResultTypeより明確になります。クラスのフィールドに意味のある名前を付けることができます(タプルの場合、それらは呼び出さItem1れますItem2)。2つのフィールドのタイプが同じ場合、これはさらに重要です。名前によってフィールドが明確に区別されます。


もう1つの小さな利点:クラスのパラメーターを安全に並べ替えることができます。タプルに対してこれを行うと、コードが破損します。フィールドに同様のタイプがある場合、これはコンパイル時に検出されない可能性があります。

タプルは、開発者が意味のあるクラス名をグループ化して意味のあるプロパティ名を考えるエネルギーがないときに使用されます。プログラミングはコミュニケーションに関するものです。タプルはコミュニケーションのアンチパターンです。私は、マイクロソフトが開発者に設計上の適切な決定を強いるように、それを言語から除外することを望みました。
マイクゴールド

5

装飾、分類、非装飾のパターンでタプルを使用するのはどうですか?(Perlの人々のためのシュヴァルツ変換)。確かにこれは人為的な例ですが、タプルはこの種のことを処理するための良い方法のようです:

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

これで、2つの要素のObject []を使用したり、この特定の例では2つの要素の文字列[]を使用したりできます。ポイントは、内部で使用され、非常に読みやすいタプルの2番目の要素として何でも使用できたことです。


3
コンストラクタの代わりにTuple.Createを使用できます。短いです。
Kugel

匿名型はより読みやすくなります。'x'の代わりに実際の変数名を使用する場合と同様
Dave Cousineau

それはでしょう。それはSOの問題です。何年も前に投稿された回答が、新しいテクノロジーのためにクリーンアップまたは更新されることはありません。
クリントンピアス

4

IMOこれらの「タプル」は基本的に、名前のないメンバーを持つすべてのパブリックアクセス匿名structタイプです。

唯一のあなたはすぐに非常に限られた範囲では、一緒にいくつかのデータをBLOBに必要があるとき、私はタプルを使用する場所です。 データのセマンティクスは明白である必要があるため、コードを読み取るのは難しくありません。したがって、(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?
}

名前付きメンバーを持つ構造体の代わりにタプルを使用する利点は何もありません。名前のないメンバーを使用すると、コードが読みやすくなり、理解しやすくなります。

タプルはstruct、実際の名前付きメンバーを使用してを作成することを回避するための怠惰な方法として私を攻撃します。タプルを使いすぎると、あなたやあなたのコードに遭遇する誰かが名前付きメンバーを必要とする本当に感じる場合、私がこれまでに見たことがなければ、A Bad Thing™です。


3

私を判断しないでください。私はエキスパートではありませんが、C#7.xの新しいタプルを使用すると、次のようなものが返されます。

return (string Name1, int Name2)

少なくとも今は名前を付けることができ、開発者はいくつかの情報を見るかもしれません。


2

もちろん異なります!あなたが言ったように、タプルを使用すると、ローカルで消費するためにいくつかのアイテムをグループ化したいときに、コードと時間を節約できます。それらを使用して、具象クラスを渡す場合よりも一般的な処理アルゴリズムを作成することもできます。あるメソッドから別のメソッドに日付をすばやく渡すために、KeyValuePairまたはDataRow以外の何かが欲しいと思った回数を思い出せません。

一方で、それをやりすぎて、何が含まれているかを推測できるだけのタプルを渡すことは十分に可能です。クラス全体でタプルを使用する場合は、1つの具象クラスを作成することをお勧めします。

もちろん適度に使用すると、タプルはより簡潔で読みやすいコードにつながります。他の言語でのタプルの使用例については、C ++、STL、Boostを参照できますが、結局、すべての人が.NET環境に最適な方法を見つけるために実験する必要があります。


1

タプルは.NET 4では役に立たないフレームワーク機能です。C#4.0では大きな機会を逃したと思います。名前付きメンバーを持つタプルがあったので、Value1Value2などの代わりに名前でタプルのさまざまなフィールドにアクセスできました。

言語(構文)の変更が必要でしたが、非常に便利でした。


タプルはどのように「言語機能」ですか?すべての.net言語で利用できるクラスではありませんか?
Jason Webb

11
おそらく、C#にはあまり役に立たないでしょう。ただし、C。のため、System.Tupleはフレームワークに追加されませんでした。F#と他の言語間の相互運用性を容易にするために追加されました。
Joel Mueller

@ジェイソン:私はそれが言語機能だとは言いませんでした(タイプミスはしましたが、このコメントをする前に修正しました)。
Philippe Leybaert、2010年

2
@Jason-タプルを直接サポートする言語では、タプルをコンポーネント値に分解する暗黙のサポートがあります。これらの言語で記述されたコードは、通常、決してItem1、Item2プロパティにアクセスしません。let success, value = MethodThatReturnsATuple()
Joel Mueller

@ジョエル:この質問はC#としてタグ付けされているので、なんとなくC#についてです。私はまだそれを言語のファーストクラスの機能に変えることができたと思います。
Philippe Leybaert、2010年

1

値が何を表しているのかわからないため、個人的にタプルを戻り値の型として使用することはありません。タプルは、オブジェクトとは異なり値型であり、等価性を理解するため、いくつかの貴重な用途があります。このため、マルチパートキーが必要な場合はディクショナリキーとして、または複数の変数でグループ化したいがネストされたグループ化が不要な場合はGroupBy句のキーとして使用します(ネストされたグループ化が必要な人はいますか?)。極端な冗長性で問題を克服するには、ヘルパーメソッドを使用して作成できます。(Item1、Item2などを介して)頻繁にメンバーにアクセスする場合は、おそらく、構造体や匿名クラスなどの別の構成体を使用する必要があります。


0

私は両方、タプルを使用しましたTupleし、新しいValueTupleさまざまなシナリオの数であり、以下の結論に達した:使用しません

毎回、次の問題が発生しました。

  • 厳密な命名がないため、コードが読めなくなった。
  • 基本クラスDTOや子クラスDTOなどのクラス階層機能を使用できない。
  • それらが複数の場所で使用されている場合、クリーンなクラス名ではなく、これらの醜い定義をコピーして貼り付けることになります。

私の意見では、タプルはC#の機能ではなく有害です。

私はやや似ていますが、との厳しさや批判ははるかに少なくなっFunc<>ていAction<>ます。これらは多くの状況で役立ちます。特に単純なものActionFunc<type>バリアントですが、それを超えると、デリゲート型を作成する方がエレガントで読みやすく、保守が容易で、ref/ outパラメータなどの機能が多くなることがわかりました。


名前付きタプルについて言及しましValueTupleたが、それらも好きではありません。第一に、命名は強力ではなく、提案に近いものであり、暗黙のうちに、TupleまたはValueTuple同じ数とタイプのアイテムに割り当てることができるため、めちゃくちゃに非常に簡単です。第二に、継承の欠如と、定義全体を場所から場所へコピーして貼り付ける必要性は、依然として不利益です。
TA氏2018年

私はそれらの点に同意します。タプルを使用するのは、プロデューサーとコンシューマーを制御している場合で、それらが「遠く」離れていない場合のみです。同じ方法での生産者と消費者の意味=「閉じる」、異なるアセンブリの生産者と消費者=「光の年」。では、メソッドの最初にタプルを作成し、最後にそれらを使用する場合はどうでしょうか。確かに、私はそれで大丈夫です。条件なども文書化します。しかし、ええ、私は一般的にそれらが正しい選択ではないことに同意します。
Mike Christiansen
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.