回答:
それがポイントです- 常にカスタムクラスや構造体を作成しない方が便利です。それはAction
orのような改善Func
です...このタイプを自分で作成できますが、フレームワークに存在するのが便利です。
convenient not to make a custom class
。を使用してカスタムクラスインスタンスを作成することを意味します=new..()
か?
タプルを使用すると、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にタプルを追加する際の腹立たしと設計上の考慮事項について述べた優れた記事があります。値タイプと参照タイプのどちらを選択するかは特に興味深いです。
記事が明らかにしているように、タプルの背後にある原動力は、それを使用するMicrosoft内の非常に多くのグループでした。言及されていませんが、C#(およびVB.NET)の新しい「動的」キーワードもそれと関係があると考えています。動的言語ではタプルが非常に一般的です。
それ以外の点では、独自のpocoを作成するよりも特に優れているわけではありません。少なくとも、メンバーにもっと良い名前を付けることができます。
更新:C#バージョン7での大幅な改訂が予定されているため、より多くの構文が使用できるようになりました。このブログ投稿の予備発表。
これは小さな例です-ユーザー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");
}
Tuple<bool, T> TryParse<T>(string input)
、出力パラメーターを使用する代わりに、両方の値をタプルで取得できます。
タプルを使用して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);
}
}
これにはカスタムタイプを使用することもできましたが、タプルとまったく同じように見えます。
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を保持するタプルです。
new Tuple<Guid,string,...>
それらはそれ自体を説明しないコードを生成するので、それらの悪用は好きではありませんが、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#プログラマをすべて考慮する必要があります。
タプルは関数型言語で頻繁に使用されており、より多くのことを実行できます。F#は、C#から相互運用し、2つの言語で記述されたコード間でやり取りしたい「公式」の.net言語です。
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
。
私の頭の上のいくつかの例:
たとえば、Point / PointFとSize / SizeFを使用するためだけに、WebアプリケーションにSystem.Drawingを含めたくありません。
Tuple
は、グラフィック操作を実行する場合と同様に、Point
または重要な状況SomeVector
で便利です。互いに非常に関連している2つの値を強調表示します。コードを読み取るときにStartTimeとEndTimeという名前のプロパティがよく見られますが、日付と時間よりも優れているため、タプルはビジネスロジックのこの領域で操作するたびに両方の値を考慮するように強制します。または、「データが変更されました(bool)、ここにデータ(その他のタイプ)があります」という集中的なバインディングを経由する代わりに、大量の処理が返されます。
Tuple
これを行う前に、慎重に使用する必要があります。以前の経験から、使用Tuple
するとコードが読みにくくなり、将来のサポートが非常に困難になることがわかりました。少し前に、タプルがほとんどどこでも使用されているコードを修正する必要がありました。適切なオブジェクトモデルについて考える代わりに、タプルを使用しました。それは悪夢だった...コードを書いた人を殺したかった...
あなたが使うべきではないと言ったくはありませんTuple
、それは悪か何かであり、私Tuple
が使用する最良の候補であるいくつかのタスクがあると確信していますが、おそらくあなたはもう一度考えるべきです、本当にそれは必要ですか? ?
タプルで私の問題の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);
私のグループに新しいクラスを宣言することでこれを行うことができましたが、新しいクラスを宣言せずにそのような匿名のコレクションを作成するという考えです。
私の場合、非同期メソッドで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).
}
タプルの詳細については、こちらをご覧ください。お役に立てれば。
オブジェクトをワイヤーを介して送信するか、アプリケーションの別のレイヤーに渡す必要があり、複数のオブジェクトが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、またはそれ以上の値を返す必要がある場合、扱いにくくなる可能性があります。複数の値を返すもう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));
もちろん、埋め込みタプルのそれぞれの末尾にタプルを追加して、必要なサイズのタプルを作成できます。
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 };
}
}
}
}