web-dev-qa-db-ja.com

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

.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が何を表しているのかを調べて把握する必要がありますが、作成および保守するコードは少なくなります。

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

166
Jason Webb

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

ただし、パブリックAPIでは、それらの効果は低くなります。消費者(あなたではない)は、特にTuple<int, int>

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

この回答 にも情報があります。

162
Bryan Watts

私の見方では、タプルは結果クラスを書くためのショートカットです(他の用途もあると確信しています)。

_Tuple<>_には確かに他の有用な用途があります-それらのほとんどは、同様の構造を共有する型の特定のグループのセマンティクスを抽象化し、単純に順序付きセットとして扱うことを伴います値の。すべての場合において、タプルの利点は、メソッドではなくプロパティを公開するデータのみのクラスで名前空間が乱雑になることを回避することです。

_Tuple<>_の合理的な使用例は次のとおりです。

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

上記の例では、一対の対戦相手を表現したいので、タプルは、新しいクラスを作成せずにこれらのインスタンスをペアリングする便利な方法です。別の例を次に示します。

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

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

tuplesのポイントを見逃している可能性はさておき、Tupleを使用した例は悪い設計選択ですか?

パブリック型のパブリックAPIの一部として強く型付けされた_Tuple<>_インスタンスを返すことはめったに良いアイデアではありません。ご存じのように、タプルには関係者(ライブラリ作成者、ライブラリユーザー)使用されているタプル型の目的と解釈につ​​いて事前に同意すること。 _Tuple<>_を公に使用するだけではAPIの意図と動作がわかりにくくなるため、直感的で明確なAPIを作成することは十分に困難です。

匿名型もTupleの一種です-ただし、それらは厳密に型指定されており、その型に属するプロパティに明確で有益な名前を指定できます。しかし、匿名型はさまざまな方法で使用することは困難です。これらは主に、通常は名前を割り当てたくない型を生成するLINQなどのテクノロジーをサポートするために追加されました。 (はい、同じ型と名前付きプロパティを持つ匿名型がコンパイラによって統合されることを知っています)。

私の経験則は:パブリックインターフェイスから返す場合-名前付きタイプにするです。

タプルを使用するための他の経験則は次のとおりです:メソッド引数とタイプ_Tuple<>_の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 );
        }
    }
}
_

PS辞書の匿名型に起因する問題を回避する別の(より簡単な)方法があります。つまり、varキーワードを使用して、コンパイラにあなたのために入力します。そのバージョンは次のとおりです。

_var foundItem = peopleDict.FirstOrDefault().Value;
if( peopleDict.TryGetValue( "Smith", out foundItem ) )
{
   // use foundItem...
}
_
79
LBushkin

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

18
Matthew Whited

タプルは、C#プログラマーの観点から見ると、CLRへの追加がかなり圧倒的です。長さが異なるアイテムのコレクションがある場合、コンパイル時に一意の静的名を持つ必要はありません。

しかし、一定の長さのコレクションがある場合、これはコレクション内の固定された場所がそれぞれ特定の事前定義された意味を持つことを意味します。また、Item1Item2などの重要性を覚える必要はなく、その場合は適切な静的名を付ける方がalwaysの方が適切です。

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

キーワード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
johnny g

飾り付け-並べ替え-非装飾のパターンでタプルを使用してはどうですか? (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番目の要素として何でも使用できたということです。

5
Clinton Pierce

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

5
Richard Fearn

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

only Tupleを使用する場所は、非常に限られた範囲でいくつかのデータをすばやく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?
}

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

タプルは、実際の名前付きメンバーでstructを作成するのを避けるための怠zyな方法だと思います。 Tupleの使いすぎ。コードに遭遇したあなたや他の誰かが本当に必要だと感じる場所named membersは、私が見たことがあるなら、A Bad Thing™です。

4
bobobobo

私を判断しないでください、私は専門家ではありませんが、C#7.xの新しいタプルでは、​​次のようなものを返すことができます。

return (string Name1, int Name2)

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

3
Denis Gordin

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

一方、無理をして、タプルを渡して、そこに含まれているものしか推測できない場合もあります。クラス間でTupleを使用する場合、おそらく1つの具象クラスを作成する方が良いでしょう。

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

2

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

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

1

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

1
Seth Paulson

さまざまなシナリオでTupleと新しいValueTupleの両方のタプルを使用し、次の結論に達しました:使用しない

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

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

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

多少似ていますが、Func<>およびAction<>。これらは多くの状況、特に単純なActionFunc<type>バリアントですが、それ以上のものは、デリゲート型の作成がよりエレガントで読みやすく、保守しやすく、ref/outパラメーターなどの機能が追加されていることがわかりました。

0
Mr. TA