Z mojego punktu widzenia krotka jest skrótem do napisania klasy wynikowej (jestem pewien, że są też inne zastosowania).
Istnieją rzeczywiście inne cenne zastosowaniaTuple<>
- większość z nich polega na abstrahowaniu od semantyki określonej grupy typów, które mają podobną strukturę, i traktowaniu ich po prostu jako uporządkowanego zbioru wartości. We wszystkich przypadkach zaletą krotek jest to, że pozwalają uniknąć zaśmiecania przestrzeni nazw klasami tylko do danych, które ujawniają właściwości, ale nie metody.
Oto przykład rozsądnego użycia Tuple<>
:
var opponents = new Tuple<Player,Player>( playerBob, playerSam );
W powyższym przykładzie chcemy reprezentować parę przeciwników, krotka jest wygodnym sposobem parowania tych instancji bez konieczności tworzenia nowej klasy. Oto kolejny przykład:
var pokerHand = Tuple.Create( card1, card2, card3, card4, card5 );
Układ w pokera można traktować jako po prostu zestaw kart - a krotka (może być) rozsądnym sposobem wyrażenia tego pojęcia.
odkładając na bok możliwość, że brakuje mi sensu krotek, czy przykład z krotką jest złym wyborem projektowym?
Zwracanie silnie wpisanych Tuple<>
wystąpień jako części publicznego interfejsu API dla typu publicznego rzadko jest dobrym pomysłem. Jak sam wiesz, krotki wymagają od zaangażowanych stron (autora biblioteki, użytkownika biblioteki) uzgodnienia z wyprzedzeniem celu i interpretacji używanych typów krotek. Tworzenie interfejsów API, które są intuicyjne i przejrzyste, a ich Tuple<>
publiczne używanie jedynie przesłania intencje i zachowanie API, jest wystarczająco trudne .
Typy anonimowe są również rodzajem krotki - są jednak silnie wpisane i pozwalają określić jasne, informacyjne nazwy dla właściwości należących do typu. Ale typy anonimowe są trudne w użyciu w różnych metodach - zostały one głównie dodane do obsługi technologii, takich jak LINQ, w których projekcje tworzyłyby typy, do których normalnie nie chcielibyśmy przypisywać nazw. (Tak, wiem, że typy anonimowe o tych samych typach i nazwanych właściwościach są konsolidowane przez kompilator).
Moja praktyczna zasada brzmi: jeśli zwrócisz go z publicznego interfejsu - nadaj mu nazwany typ .
Moja druga praktyczna zasada dotycząca używania krotek to: nazwa argumenty metody i zmienne lokalne typu Tuple<>
tak jasno, jak to tylko możliwe - spraw, aby nazwa przedstawiała znaczenie relacji między elementami krotki. Pomyśl o moim var opponents = ...
przykładzie.
Oto przykład rzeczywistego przypadku, w którym Tuple<>
unikałem deklarowania typu tylko do danych do użytku tylko w moim własnym zestawie . Sytuacja ta polega na tym, że przy używaniu słowników generycznych zawierających typy anonimowe trudno jest użyć TryGetValue()
metody do wyszukiwania elementów w słowniku, ponieważ metoda wymaga out
parametru, którego nie można nazwać:
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 Istnieje inny (prostszy) sposób na obejście problemów wynikających z anonimowych typów w słownikach, a jest to użycie var
słowa kluczowego, aby kompilator „wywnioskował” typ za Ciebie. Oto ta wersja:
var foundItem = peopleDict.FirstOrDefault().Value;
if( peopleDict.TryGetValue( "Smith", out foundItem ) )
{
// use foundItem...
}