.Net 4で導入されたタプルを見ましたが、どこで使用できるか想像できません。いつでもカスタムクラスまたはStructを作成できます。
それがポイントです-常にカスタムクラスまたは構造体を作成する方が便利ですnotです。 Action
やFunc
のような改良です...この型は自分で作成できますが、フレームワークに存在すると便利です。
タプルを使用すると、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
MSDNマガジンには 優秀な記事 があり、BCLにTupleを追加する際の腹を立てるデザインの考慮事項について説明しています。特に興味深いのは、値型と参照型の選択です。
記事が明らかにしているように、Tupleの背後にある推進力は、Microsoftの内部で非常に多くのグループが使用しており、F#チームが前もって使用していました。言及していませんが、C#(およびVB.NET)の新しい「動的」キーワードも同様に関係していると考えています。動的言語ではタプルは非常に一般的です。
それ以外の場合、独自のpocoを作成するよりも特に優れているわけではありません。少なくともメンバーにもっと良い名前を付けることができます。
更新:C#バージョン7の大幅な改訂により、より多くの構文が愛用されるようになりました。 このブログ投稿 での事前のお知らせ。
タプルを使用して解決しました プロジェクトオイラーの問題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);
}
}
I couldはこのためにカスタムタイプを使用しましたが、まさにタプルのようにに見えるはずです。
次に小さな例を示します-ユーザーIDを指定して、ユーザーのハンドルとメールアドレスを検索する必要があるメソッドがあるとします。そのデータを含むカスタムクラスを常に作成するか、そのデータにref/outパラメーターを使用するか、Tupleを返し、新しいPOCOを作成せずにNiceメソッドシグネチャを持つことができます。
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");
}
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
は、ある状態をタプルに変換します。 Tupleから最初の値を返し、新しい状態を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
は、実際にはそれぞれfib(n-2)とfib(n-1)を保持するタプルです。
彼らはそれ自体を説明しないコードを生成するため、それらの乱用は好きではありませんが、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));
}
タプルは、一度に複数の非同期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#プログラマー全員を考慮する必要があります。
読みやすさを損なうため、ほとんどのシナリオで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
を作成します。いくつかのオプションがあります:
Dictionary<string, Dictionary<string, int>>
。Dictionary<CarAndCity, int>
。Dictionary<Tuple<string, string>, int>
。最初のオプションでは読みやすさが失われます。もっと多くのコードを書く必要があります。
2番目のオプションは機能し、簡潔ですが、車と都市は実際には関連しておらず、おそらく一緒にクラスに属していません。
3番目のオプションは簡潔でクリーンです。 Tuple
の適切な使用法です。
タプルは関数型言語で頻繁に使用され、より多くのことができるようになりました。F#は、C#から相互運用し、2つの言語で記述されたコード間で渡すことができる「公式」.net言語です。
私の頭の上のいくつかの例:
たとえば、Point/PointFとSize/SizeFを使用するためだけにWebアプリケーションにSystem.Drawingを含めたくないでしょう。
Tuple
の使用には非常に注意し、おそらくこれを行う前によく考えてください。以前の経験から、Tuple
を使用すると、コードの読み取りとサポートが非常に困難になることがわかりました。少し前に、タプルがほぼどこでも使用されていたコードを修正する必要がありました。適切なオブジェクトモデルについて考える代わりに、タプルを使用しました。それは悪夢だった...時にはコードを書いた男を殺したかった...
Tuple
を使用するべきではないと言ってはいけません。それは悪か何かであり、Tuple
を使用するのが最良の候補であるタスクがあることは100%確信していますが、おそらくもう一度考えてみてください、本当に必要ですか?
タプルで私の問題の解決策を見つけました。メソッドのスコープ内でクラスを宣言するようなものですが、フィールド名の遅延宣言があります。タプルのコレクションとその単一のインスタンスを操作し、必要なフィールド名を使用して、タプルに基づいて匿名型のコレクションを作成します。これにより、この目的のために新しいクラスを作成する必要がなくなります。
タスクは、追加のクラスなしで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);
私のグループのために新しいクラスを宣言することでこれを行うことができますが、新しいクラスを宣言せずにそのような匿名のコレクションを作成するという考えです。
私の場合、非同期メソッドでoutパラメーターを使用できないことがわかったとき、Tupleを使用する必要がありました。それについて読む こちら 。また、別の戻り値の型が必要でした。そのため、代わりに戻り型としてTupleを使用し、メソッドを非同期としてマークしました。
以下のサンプルコード。
...
...
// 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).
}
Tuple here の詳細をご覧ください。お役に立てれば。
私が見つけたタプルの最適な使用法は、メソッドから複数のタイプのオブジェクトを返す必要があり、オブジェクトのタイプと数を知っている場合で、長いリストではありません。
他の単純な代替手段は、「出力」パラメータを使用することです
private string MyMethod(out object)
または辞書を作る
Dictionary<objectType1, objectType2>
ただし、タプルを使用すると、「out」オブジェクトを作成したり、辞書のエントリを本質的に検索する必要がなくなります。
C#7で同じ問題を解決するために3つの方法を試してみましたが、タプルのユースケースが見つかりました。
マッピングなどを行う場合、Webプロジェクトで動的データを使用するのは面倒な場合があります。
Tupleがitem1、item2、itemNに自動マッピングされる方法が好きです。これは、インデックス外のアイテムでキャッチされる可能性のある配列インデックスを使用したり、プロパティ名のスペルを間違える可能性のある匿名タイプを使用するよりも堅牢なようです。
Tupleを使用するだけでDTOが無料で作成されたように感じられ、そのために個別のDTOを作成することなく、静的型付けのように感じるitemNを使用してすべてのプロパティにアクセスできます。
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 };
}
}
}
}
プロトタイピング専用-タプルは無意味です。それらを使用すると便利ですが、ショートカットのみです!プロトタイプの場合-結構です。後でこのコードを必ず削除してください。
書きやすく、読みにくい。クラス、内部クラス、匿名クラスなどに対して目に見える利点はありません。
ワイヤーを介してオブジェクトを送信したり、アプリケーションの別のレイヤーに渡す必要があるときにオブジェクトの形状を変更すると、複数のオブジェクトが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;
}
返す必要のある値が数個しかない場合、outパラメーターは優れていますが、4、5、6、またはそれ以上の値を返す必要がある場合は、扱いにくくなる可能性があります。複数の値を返すための別のオプションは、ユーザー定義のクラス/構造を作成して返すか、メソッドによって返される必要があるすべての値をパッケージ化するためにタプルを使用することです。
クラス/構造を使用して値を返す最初のオプションは簡単です。次のような型(この例では構造体)を作成します。
public struct Dimensions
{
public int Height;
public int Width;
public int Depth;
}
タプルを使用する2番目のオプションは、ユーザー定義オブジェクトを使用するよりもエレガントなソリューションです。タプルを作成して、さまざまなタイプの値をいくつでも保持できます。さらに、タプルに保存するデータは不変です。コンストラクターまたは静的Createメソッドを使用してデータをTupleに追加すると、そのデータは変更できません。タプルは、最大8つの個別の値を受け入れることができます。 8つ以上の値を返す必要がある場合は、特別なTupleクラスを使用する必要があります。Tupleクラス8つ以上の値を持つTupleを作成する場合、静的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));
もちろん、埋め込まれた各タプルの最後にさらにタプルを追加して、必要なサイズのタプルを作成できます。