.Net 4.0でタプルを使用できる実際的な例は?


97

.Net 4で導入されたタプルを見たことがありますが、どこで使用できるのか想像できません。いつでもカスタムクラスまたはStructを作成できます。


13
たぶん、このトピックは非常に古いので注意してください。C#7で何が起こったのか見てください!
maf-soft 2017

回答:


83

それがポイントです- 常にカスタムクラスや構造体を作成しない方が便利です。それはActionorのような改善Funcです...このタイプを自分で作成できますが、フレームワークに存在するのが便利です。


5
MSDNから指摘する価値があると思います:「この型のすべてのパブリック静的メンバーはスレッドセーフです。インスタンスメンバーはスレッドセーフであるとは限りません。
Matt Borja

おそらく、言語デザイナーは、その場でカスタム型を簡単に作成できるはずだったのではないでしょうか。したがって、さらに別の構文を導入する代わりに、同じ構文を維持できますか?
Thomas Eyde 2017年

@ThomasEyde。これは、彼らが過去15年間行ってきたこととまったく同じです。このようにして、式本体のメンバーが追加され、現在はタプルを評価しています。録音スタイルのC#7のこのバージョンのために狭く(9ヶ月、このコメントの前に)8月中のカットバックを逃したクラス、そしておそらくこともC#8ノートに出てきている値のプレーン古いクラスがないところタプルが価値の平等を提供します。2002年にこれらすべてを導入するには、先見性が必要です
Panagiotis Kanavos

どういう意味ですかconvenient not to make a custom class。を使用してカスタムクラスインスタンスを作成することを意味します=new..()か?
Alex、

75

タプルを使用すると、2次元の辞書(または、n次元)を簡単に実装できます。たとえば、このようなディクショナリを使用して、通貨交換マッピングを実装できます。

var forex = new Dictionary<Tuple<string, string>, decimal>();
forex.Add(Tuple.Create("USD", "EUR"), 0.74850m); // 1 USD = 0.74850 EUR
forex.Add(Tuple.Create("USD", "GBP"), 0.64128m);
forex.Add(Tuple.Create("EUR", "USD"), 1.33635m);
forex.Add(Tuple.Create("EUR", "GBP"), 0.85677m);
forex.Add(Tuple.Create("GBP", "USD"), 1.55938m);
forex.Add(Tuple.Create("GBP", "EUR"), 1.16717m);
forex.Add(Tuple.Create("USD", "USD"), 1.00000m);
forex.Add(Tuple.Create("EUR", "EUR"), 1.00000m);
forex.Add(Tuple.Create("GBP", "GBP"), 1.00000m);

decimal result;
result = 35.0m * forex[Tuple.Create("USD", "EUR")]; // USD 35.00 = EUR 26.20
result = 35.0m * forex[Tuple.Create("EUR", "GBP")]; // EUR 35.00 = GBP 29.99
result = 35.0m * forex[Tuple.Create("GBP", "USD")]; // GBP 35.00 = USD 54.58

国の省略形にはEnumを使用したいと思います。それは可能ですか?
ザック

1
列挙型は値型なので、問題なく動作するはずです。
MarioVW 2014

@MarioVW多次元配列を使用して実現することもできます。タプルはどのように違いますか?
Alex、

26

MSDNマガジンには、BCLにタプルを追加する際の腹立たしと設計上の考慮事項について述べた優れた記事があります。値タイプと参照タイプのどちらを選択するかは特に興味深いです。

記事が明らかにしているように、タプルの背後にある原動力は、それを使用するMicrosoft内の非常に多くのグループでした。言及されていませんが、C#(およびVB.NET)の新しい「動的」キーワードもそれと関係があると考えています。動的言語ではタプルが非常に一般的です。

それ以外の点では、独自のpocoを作成するよりも特に優れているわけではありません。少なくとも、メンバーにもっと良い名前を付けることができます。


更新:C#バージョン7での大幅な改訂が予定されているため、より多くの構文が使用できるようになりました。このブログ投稿の予備発表。


23

これは小さな例です-ユーザーIDを指定して、ユーザーのハンドルと電子メールアドレスを検索する必要があるメソッドがあるとします。常にそのデータを含むカスタムクラスを作成するか、そのデータにref / outパラメータを使用するか、タプルを返すだけで、新しいPOCOを作成せずに優れたメソッドシグネチャを持つことができます。

public static void Main(string[] args)
{
    int userId = 0;
    Tuple<string, string> userData = GetUserData(userId);
}

public static Tuple<string, string> GetUserData(int userId)
{
    return new Tuple<string, string>("Hello", "World");
}

10
これは良い例ですが、タプルの使用を正当化するものではありません。
Amitabh 2010

7
個別の値を返すため、タプルは適切な適合です。しかし、異なるタイプの複数の値を返す場合、タプルはより輝きます
Mark Rushakoff、2010

21
別の良い例は、出力パラメーターを削除して、代わりにタプルを使用できるため、int.TryParseです。したがってTuple<bool, T> TryParse<T>(string input)、出力パラメーターを使用する代わりに、両方の値をタプルで取得できます。
Tejs

3
実際、F#から任意のTryParseメソッドを呼び出すと、まさにそれが起こります。
Joel Mueller、

23

タプルを使用してProject Eulerの問題11を解決しました:

class Grid
{
    public static int[,] Cells = { { 08, 02, 22, // whole grid omitted

    public static IEnumerable<Tuple<int, int, int, int>> ToList()
    {
        // code converts grid to enumeration every possible set of 4 per rules
        // code omitted
    }
}

これで問題全体を解決できます:

class Program
{
    static void Main(string[] args)
    {
        int product = Grid.ToList().Max(t => t.Item1 * t.Item2 * t.Item3 * t.Item4);
        Console.WriteLine("Maximum product is {0}", product);
    }
}

これにカスタムタイプを使用することできましたが、タプルとまったく同じように見えます。


16

C#のタプル構文は途方もなくかさばるので、タプルを宣言するのは大変です。また、パターンマッチングがないため、使用するのも面倒です。

ただし、クラスを作成せずに、その場限りのオブジェクトのグループ化が必要な場合もあります。たとえば、リストを集計したいが、1つではなく2つの値が必要だとします。

// sum and sum of squares at the same time
var x =
    Enumerable.Range(1, 100)
    .Aggregate((acc, x) => Tuple.Create(acc.Item1 + x, acc.Item2 + x * x));

値のコレクションを単一の結果に結合する代わりに、単一の結果を値のコレクションに拡張してみましょう。この関数を記述する最も簡単な方法は次のとおりです。

static IEnumerable<T> Unfold<T, State>(State seed, Func<State, Tuple<T, State>> f)
{
    Tuple<T, State> res;
    while ((res = f(seed)) != null)
    {
        yield return res.Item1;
        seed = res.Item2;
    }
}

f一部の状態をタプルに変換します。タプルから最初の値を返し、新しい状態を2番目の値に設定します。これにより、計算全体で状態を保持できます。

あなたはそれをそのまま使用します:

// return 0, 2, 3, 6, 8
var evens =
    Unfold(0, state => state < 10 ? Tuple.Create(state, state + 2) : null)
    .ToList();

// returns 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
var fibs =
    Unfold(Tuple.Create(0, 1), state => Tuple.Create(state.Item1, Tuple.Create(state.Item2, state.Item1 + state.Item2)))
    .Take(10).ToList();

evensかなり簡単fibsですが、もう少し賢いです。それはstate実際には(N-1)は、それぞれ、FIB(N-2)及びFIBを保持するタプルです。


4
+1 Tuple.Createは便利な省略形ですnew Tuple<Guid,string,...>
AaronLS 2014年

@Juliet州のキーワードの使用
irfandar

7

それらはそれ自体を説明しないコードを生成するので、それらの悪用は好きではありませんが、IStructuralEquatableとIStructuralComparableを実装しているため(ルックアップと順序付けの両方に使用するため)、オンザフライの複合キーを実装するのは素晴らしいです目的)。

そして、それらはすべてのアイテムのハッシュコードを内部的に結合します。たとえば、タプルのGetHashCode(ILSpyから取得)は次のとおりです。

    int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
    {
        return Tuple.CombineHashCodes(comparer.GetHashCode(this.m_Item1), comparer.GetHashCode(this.m_Item2), comparer.GetHashCode(this.m_Item3));
    }

7

タプルは、一度に複数の非同期IO操作を実行し、すべての値を一緒に返すのに最適です。タプルを使用した場合と使用しない場合の例を以下に示します。タプルは実際にコードをより明確にすることができます!

なし(厄介なネスト!):

Task.Factory.StartNew(() => data.RetrieveServerNames())
    .ContinueWith(antecedent1 =>
        {
            if (!antecedent1.IsFaulted)
            {
                ServerNames = KeepExistingFilter(ServerNames, antecedent1.Result);
                Task.Factory.StartNew(() => data.RetrieveLogNames())
                    .ContinueWith(antecedent2 =>
                        {
                            if (antecedent2.IsFaulted)
                            {
                                LogNames = KeepExistingFilter(LogNames, antecedent2.Result);
                                Task.Factory.StartNew(() => data.RetrieveEntryTypes())
                                    .ContinueWith(antecedent3 =>
                                        {
                                            if (!antecedent3.IsFaulted)
                                            {
                                                EntryTypes = KeepExistingFilter(EntryTypes, antecedent3.Result);
                                            }
                                        });
                            }
                        });
            }
        });

タプルあり

Task.Factory.StartNew(() =>
    {
        List<string> serverNames = data.RetrieveServerNames();
        List<string> logNames = data.RetrieveLogNames();
        List<string> entryTypes = data.RetrieveEntryTypes();
        return Tuple.Create(serverNames, logNames, entryTypes);
    }).ContinueWith(antecedent =>
        {
            if (!antecedent.IsFaulted)
            {
                ServerNames = KeepExistingFilter(ServerNames, antecedent.Result.Item1);
                LogNames = KeepExistingFilter(LogNames, antecedent.Result.Item2);
                EntryTypes = KeepExistingFilter(EntryTypes, antecedent.Result.Item3);
            }
        });

いずれにせよ、暗黙の型を持つ無名関数を使用していた場合は、タプルを使用してコードを明確にする必要はありません。メソッドからタプルを再調整しますか?私の控えめな意見では、コードの明快さが重要な場合は控えめに使用してください。C#の関数型プログラミングは抵抗するのが難しいことは知っていますが、これらの古い不格好な「オブジェクト指向」のC#プログラマをすべて考慮する必要があります。


5

タプルは関数型言語で頻繁に使用されており、より多くのことを実行できます。F#は、C#から相互運用し、2つの言語で記述されたコード間でやり取りしたい「公式」の.net言語です。


タプルは、PythonやRuby(相互運用用の.Net実装も備えている... IronPython / Ruby)のようないくつかの一般的なスクリプト言語の型にも組み込まれています。
MutantNinjaCodeMonkey

5

Tuple読みやすさを損なうため、ほとんどのシナリオでは回避する傾向があります。ただし、Tuple無関係なデータをグループ化する必要がある場合に役立ちます。

たとえば、自動車とそれらが購入された都市のリストがあるとします。

Mercedes, Seattle
Mustang, Denver
Mercedes, Seattle
Porsche, Seattle
Tesla, Seattle
Mercedes, Seattle

都市ごとに各車の数を集計するとします。

Mercedes, Seattle [3]
Mustang, Denver [1]
Porsche, Seattle [1]
Tesla, Seattle [1]

これを行うには、を作成しDictionaryます。いくつかのオプションがあります。

  1. を作成しますDictionary<string, Dictionary<string, int>>
  2. を作成しますDictionary<CarAndCity, int>
  3. を作成しますDictionary<Tuple<string, string>, int>

最初のオプションでは読みやすさが失われます。より多くのコードを書く必要があります。

2番目のオプションは機能し、簡潔ですが、車と都市は実際には関連がなく、おそらく一緒にクラスに属していません。

3番目のオプションは簡潔でクリーンです。それはの良い使い方ですTuple


4

私の頭の上のいくつかの例:

  • XおよびYの位置(必要に応じてZ)
  • 幅と高さ
  • 時間をかけて測定されたもの

たとえば、Point / PointFとSize / SizeFを使用するためだけに、WebアプリケーションにSystem.Drawingを含めたくありません。


2
Tupleは、グラフィック操作を実行する場合と同様に、Pointまたは重要な状況SomeVectorで便利です。互いに非常に関連している2つの値を強調表示します。コードを読み取るときにStartTimeとEndTimeという名前のプロパティがよく見られますが、日付と時間よりも優れているため、タプルはビジネスロジックのこの領域で操作するたびに両方の値を考慮するように強制します。または、「データが変更されました(bool)、ここにデータ(その他のタイプ)があります」という集中的なバインディングを経由する代わりに、大量の処理が返されます。
レオンペルティエ2015

3

Tupleこれを行う前に、慎重に使用する必要があります。以前の経験から、使用Tupleするとコードが読みにくくなり、将来のサポートが非常に困難になることがわかりました。少し前に、タプルがほとんどどこでも使用されているコードを修正する必要がありました。適切なオブジェクトモデルについて考える代わりに、タプルを使用しました。それは悪夢だった...コードを書いた人を殺したかった...

あなたが使うべきではないと言ったくはありませんTuple、それは悪か何かであり、私Tupleが使用する最良の候補であるいくつかのタスクがあると確信していますが、おそらくあなたはもう一度考えるべきです、本当にそれは必要ですか? ?


1

私が見つけたタプルの最適な使用法は、メソッドから複数のタイプのオブジェクトを返す必要があり、それらがどのオブジェクトタイプとオブジェクト番号になるかがわかっている場合で、長いリストではありません。

他の単純な代替案は、 'out'パラメータを使用することです。

private string MyMethod(out object)

または辞書を作る

Dictionary<objectType1, objectType2>

ただし、タプルを使用すると、「out」オブジェクトを作成するか、基本的に辞書でエントリを検索する必要がなくなります。


1

タプルで私の問題の1つの解決策を見つけました。これは、メソッドのスコープ内でクラスを宣言するようなものですが、フィールド名の遅延宣言を伴います。タプルのコレクションとその単一のインスタンスを操作し、タプルに基づいて、必要なフィールド名を持つ匿名型のコレクションを作成します。これにより、この目的で新しいクラスを作成する必要がなくなります。

タスクは、追加のクラスなしでLINQからのJSON応答を書き込むことです。

 //I select some roles from my ORM my with subrequest and save results to Tuple list
 var rolesWithUsers = (from role in roles
                       select new Tuple<string, int, int>(
                         role.RoleName, 
                         role.RoleId, 
                         usersInRoles.Where(ur => ur.RoleId == role.RoleId).Count()
                      ));

 //Then I add some new element required element to this collection
 var tempResult = rolesWithUsers.ToList();
 tempResult.Add(new Tuple<string, int, int>(
                        "Empty", 
                         -1,
                         emptyRoleUsers.Count()
                      ));

 //And create a new anonimous class collection, based on my Tuple list
 tempResult.Select(item => new
            {
                GroupName = item.Item1,
                GroupId = item.Item2,
                Count = item.Item3
            });


 //And return it in JSON
 return new JavaScriptSerializer().Serialize(rolesWithUsers);

私のグループに新しいクラスを宣言することでこれを行うことができましたが、新しいクラスを宣言せずにそのような匿名のコレクションを作成するという考えです。


1

私の場合、非同期メソッドでoutパラメータを使用できないことがわかったとき、タプルを使用する必要がありました。それについてはこちらをお読みください。別の戻り値の型も必要でした。そこで、代わりにタプルを戻り値の型として使用し、メソッドを非同期としてマークしました。

以下のサンプルコード。

...
...
// calling code.
var userDetails = await GetUserDetails(userId);
Console.WriteLine("Username : {0}", userDetails.Item1);
Console.WriteLine("User Region Id : {0}", userDetails.Item2);
...
...

private async Tuple<string,int> GetUserDetails(int userId)
{
    return new Tuple<string,int>("Amogh",105);
    // Note that I can also use the existing helper method (Tuple.Create).
}

タプルの詳細については、こちらをご覧ください。お役に立てれば。


匿名型または配列(または動的コンテンツ)を返したくなかったと思います
ozzy432836

0

オブジェクトをワイヤーを介して送信するか、アプリケーションの別のレイヤーに渡す必要があり、複数のオブジェクトが1つにマージされるときに、オブジェクトの形状を変更します。

例:

var customerDetails = new Tuple<Customer, List<Address>>(mainCustomer, new List<Address> {mainCustomerAddress}).ToCustomerDetails();

ExtensionMethod:

public static CustomerDetails ToCustomerDetails(this Tuple<Website.Customer, List<Website.Address>> customerAndAddress)
    {
        var mainAddress = customerAndAddress.Item2 != null ? customerAndAddress.Item2.SingleOrDefault(o => o.Type == "Main") : null;
        var customerDetails = new CustomerDetails
        {
            FirstName = customerAndAddress.Item1.Name,
            LastName = customerAndAddress.Item1.Surname,
            Title = customerAndAddress.Item1.Title,
            Dob = customerAndAddress.Item1.Dob,
            EmailAddress = customerAndAddress.Item1.Email,
            Gender = customerAndAddress.Item1.Gender,
            PrimaryPhoneNo = string.Format("{0}", customerAndAddress.Item1.Phone)
        };

        if (mainAddress != null)
        {
            customerDetails.AddressLine1 =
                !string.IsNullOrWhiteSpace(mainAddress.HouseName)
                    ? mainAddress.HouseName
                    : mainAddress.HouseNumber;
            customerDetails.AddressLine2 =
                !string.IsNullOrWhiteSpace(mainAddress.Street)
                    ? mainAddress.Street
                    : null;
            customerDetails.AddressLine3 =
                !string.IsNullOrWhiteSpace(mainAddress.Town) ? mainAddress.Town : null;
            customerDetails.AddressLine4 =
                !string.IsNullOrWhiteSpace(mainAddress.County)
                    ? mainAddress.County
                    : null;
            customerDetails.PostCode = mainAddress.PostCode;
        }
...
        return customerDetails;
    }

0

outパラメータは、返す必要がある値が少ない場合に最適ですが、4、5、6、またはそれ以上の値を返す必要がある場合、扱いにくくなる可能性があります。複数の値を返すもう1つのオプションは、ユーザー定義のクラス/構造を作成して返すか、タプルを使用してメソッドが返す必要のあるすべての値をパッケージ化することです。

クラス/構造を使用して値を返す最初のオプションは簡単です。次のようにタイプ(この例では構造)を作成します。

public struct Dimensions
{
public int Height;
public int Width;
public int Depth;
}

タプルを使用する2番目のオプションは、ユーザー定義オブジェクトを使用するよりもさらにエレガントなソリューションです。タプルは、さまざまなタイプの値をいくつでも保持するように作成できます。さらに、タプルに格納するデータは不変です。コンストラクターまたは静的Createメソッドを介してタプルにデータを追加すると、そのデータは変更できなくなります。タプルは8つまでの個別の値を受け入れることができます。8つを超える値を返す必要がある場合は、特別なタプルクラスを使用する必要があります。タプルクラス8つを超える値を持つタプルを作成する場合、静的なCreateメソッドは使用できません。代わりに、クラスのコンストラクターを使用する必要があります。これは、10個の整数値のタプルを作成する方法です。

var values = new Tuple<int, int, int, int, int, int, int, Tuple<int, int, int>> (
1, 2, 3, 4, 5, 6, 7, new Tuple<int, int, int> (8, 9, 10));

もちろん、埋め込みタプルのそれぞれの末尾にタプルを追加して、必要なサイズのタプルを作成できます。


0

プロトタイピングのみ-タプルは無意味です。それらを使用するのは便利ですが、それはショートカットのみです!プロトタイプ用-結構です。後でこのコードを必ず削除してください。

書きやすく、読みにくい。クラス、内部クラス、匿名クラスなどに比べて目に見える利点はありません。


0

C#7で同じ問題を解決するために3つの方法を試しましたが、タプルの使用例を見つけました。

Webプロジェクトで動的データを操作することは、マッピングなどの際に面倒な場合があります。

タプルがitem1、item2、itemNに自動的にマッピングされる方法が好きです。これは、インデックスの外の項目に引っ掛かる可能性がある配列インデックスを使用したり、プロパティ名のスペルを間違える可能性がある匿名型を使用したりするよりも、私にはより堅牢に思えます。

タプルを使用するだけで無料でDTOが作成されたように感じ、静的な型付けのように感じるitemNを使用してすべてのプロパティにアクセスできます。そのために別のDTOを作成する必要はありません。

using System;

namespace Playground
{
    class Program
    {
        static void Main(string[] args)
        {
            var tuple = GetTuple();
            Console.WriteLine(tuple.Item1);
            Console.WriteLine(tuple.Item2);
            Console.WriteLine(tuple.Item3);
            Console.WriteLine(tuple);

            Console.WriteLine("---");

            var dyn = GetDynamic();
            Console.WriteLine(dyn.First);
            Console.WriteLine(dyn.Last);
            Console.WriteLine(dyn.Age);
            Console.WriteLine(dyn);

            Console.WriteLine("---");

            var arr = GetArray();
            Console.WriteLine(arr[0]);
            Console.WriteLine(arr[1]);
            Console.WriteLine(arr[2]);
            Console.WriteLine(arr);

            Console.Read();

            (string, string, int) GetTuple()
            {
                return ("John", "Connor", 1);
            }

            dynamic GetDynamic()
            {
                return new { First = "John", Last = "Connor", Age = 1 };
            }

            dynamic[] GetArray()
            {
                return new dynamic[] { "John", "Connor", 1 };
            }
        }
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.