2011-05-10 9 views
21

Jestem wielkim fanem domeny .NET 4.0 Tuple classes.Dlaczego powinienem unikać tworzenia klasy MutableTuple <T1,T2,TEtc> w języku C#?

Wszystkie przedmioty w krotkach są niezmienne. Istnieją oczywiście przypadki, w których jest to korzystne (oczywiście gdy krotki są używane do reprezentowania ad hoc ValueType, dla którego nie ma deklaracji).

Mam jednak kilka przypadków użycia, w których mogłem zobaczyć korzyści dla elementów Tuple z ustawieniami (z wyjątkiem parametru typu TRest w Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>). Biorąc pod uwagę, że mam dostęp do źródła i do Matt Ellis's article on "Building Tuple", wydaje się, że byłoby całkiem proste wdrożenie takiego MutableTuple<T1,T2,TEtc>.

Firma Microsoft podjęła zdecydowaną decyzję o niezmienności numeru Tuple. Czy istnieje powód, dla którego przeoczyłem to, że nie powinienem tworzyć alternatywnej implementacji z zmiennymi nie-krotkami?

+8

Dlaczego po prostu nie stworzysz małej klasy, która zawiera potrzebne informacje, wraz z właściwościami i wszystkimi? – Chad

+0

@Chad, ponieważ jest to więcej pracy niż potrzeba – smartcaveman

+0

możliwy duplikat [Dlaczego pozycje Tuple są Czytane?] (Http://stackoverflow.com/questions/3131400/why-tuples-items-are-readonly) – nawfal

Odpowiedz

24

Moim zdaniem, klasy Tuple powinny być zwykle używane tylko do reprezentowania danych w krótkim scenariuszu i tylko do wewnętrznych szczegółów implementacji. Na przykład zapewniają wygodę podczas zwracania wielu wartości z metody prywatnej podczas refaktoryzacji.

Gdy tylko wartość stanie się częścią publicznego interfejsu API, lub dłużej będzie ona dłużej działać w interfejsie API, osobiście uważam, że z punktu widzenia łatwości administrowania staje się o wiele lepszy, aby użyć niestandardowej klasy lub struktury zawierającej dokładne właściwości potrzeba, z odpowiednimi nazwami.

Jako taka - "zmienna krotka" będzie z definicji czymś, co powstaje z myślą o dłuższym cyklu życia - jeśli masz zamiar go stworzyć, a potem zmutować, jesteś efektywny mówiąc, że przedmiot istnieje w jakimś celu. Polecam niestandardowej klasy do przechowywania tych danych w tym momencie, gdyż zapewnia wiele korzyści w zakresie konserwacji, w tym:

  • Proper nazewnictwa zmiennych
  • Możliwość dodawania walidacji w klasie zawierającej dane

Drugi punkt, szczególnie, staje się bardzo ważny w miarę upływu czasu - jeśli zamierzasz mutować dane, prawdopodobnie będziesz musiał je zweryfikować w pewnym momencie, aby sprawdzić, czy wartości są odpowiednie. Jeśli używasz krotki, ta walidacja musiałaby wystąpić poza klasą zawierającą dane, co może znacznie zmniejszyć łatwość konserwacji twojego kodu.

+0

w rzeczywistości, moje przypadek użycia nie pociąga za sobą "dłuższego cyklu życia". Planowany cykl życia dla mojego przypadku użycia mieści się w zakresie metody rozszerzenia. Rozważ implikacje dla Fluent API korzystające z rozszerzeń: (1) 'Func , TResult> Tupled (to Func func)' i (2) 'TResult InvokeWithContract (to Func func, Func validatePreCondition, Akcja resolvePreConditionFail, Func validatePostCondition, Akcja resolvePostConditionFail, T args) gdzie T: IStructuralEquatable, IStructuralComparable' – smartcaveman

+0

Chociaż ja uznaję, że można to osiągnąć z 'Func zamiast tego delegować, oznaczałoby to, że nie mógłbym użyć ogólnego parametru 'in', a także, że mój kod nie byłby tak zwięzły. Czy nadal uważasz, że MutableTuple to zła decyzja w tej konkretnej sytuacji? – smartcaveman

+0

@smartcaveman: Osobiście nadal będę traktował to jako niezmienne, a jeśli musisz to zmienić, skopiuj członków do nowej krotki. W związku z tym, w przypadku takiego API, prawdopodobnie użyłbym klasy niestandardowej - szczególnie, jeśli będzie ona używana w publicznym interfejsie API ... –

1

Niezmienna klasa Tuple odnosi się do koncepcji programowania funkcjonalnego. W ramach tych koncepcji oczekuje się, że wszystkie zmienne będą niezmienne. Prawidłowy sposób na "zmianę" wartości w tej dziedzinie polega na utworzeniu kopii ze zmienionymi wartościami. Możesz to łatwo osiągnąć, przekazując oryginalną krotkę i nową wartość, utwórz nową krotkę ze starymi wartościami plus nową i zwróć nową krotkę. Zapewnia to, że każdy kod, który tworzy krotkę, może zagwarantować, że nie zostanie zmodyfikowany po przekazaniu do innej funkcji. Jeśli utworzyłeś zmienną klasę, nie nazwałbym tego Tuple, co jest sprzeczne z konwencjonalnymi oczekiwaniami.

Możesz mieć więcej szczęścia używając czegoś takiego jak f # do implementacji. Ma funkcje wyższego rzędu, typy wnioskowania i wyrażenia obliczeniowe, które nie koniecznie ograniczając złożoność kodu, zwiększyłyby czytelność skokowo.

3

Stworzyłem wersję do odczytu/zapisu dla Afterthought. Jednak w oparciu o opinie API ze społeczności, wybrałem zmianę interfejsu API, aby uczynić ją niepotrzebną. Jednak moja początkowa potrzeba była podobna do twojej, ponieważ zawierała mocno wpisane wartości parametrów dla metod i wymagała zachowania podobnego do Tuple, które było odczytywane/zapisywane, a także kompatybilne z .NET 3.5. Oto moja realizacja, minus wsparcie dla TREST:

/// <summary> 
/// Abstract base class for generic <see cref="Parameter<T1>"/> family of classes 
/// that represent parameters passed to constructors and methods. 
/// </summary> 
/// <remarks> 
/// This class was created due to a desire to support .NET 3.5, which does not 
/// include the <see cref="Tuple"/> class providing similar capabilities 
/// </remarks> 
public abstract class Parameter 
{} 

public class Parameter<T1> : Parameter 
{ 
    public Parameter(T1 param1) 
    { 
     this.Param1 = param1; 
    } 

    public T1 Param1 { get; set; } 
} 

public class Parameter<T1, T2> : Parameter<T1> 
{ 
    public Parameter(T1 param1, T2 param2) 
     : base(param1) 
    { 
     this.Param2 = param2; 
    } 

    public T2 Param2 { get; set; } 
} 

public class Parameter<T1, T2, T3> : Parameter<T1, T2> 
{ 
    public Parameter(T1 param1, T2 param2, T3 param3) 
     : base(param1, param2) 
    { 
     this.Param3 = param3; 
    } 

    public T3 Param3 { get; set; } 
} 

public class Parameter<T1, T2, T3, T4> : Parameter<T1, T2, T3> 
{ 
    public Parameter(T1 param1, T2 param2, T3 param3, T4 param4) 
     : base(param1, param2, param3) 
    { 
     this.Param4 = param4; 
    } 

    public T4 Param4 { get; set; } 
} 

public class Parameter<T1, T2, T3, T4, T5> : Parameter<T1, T2, T3, T4> 
{ 
    public Parameter(T1 param1, T2 param2, T3 param3, T4 param4, T5 param5) 
     : base(param1, param2, param3, param4) 
    { 
     this.Param5 = param5; 
    } 

    public T5 Param5 { get; set; } 
} 

public class Parameter<T1, T2, T3, T4, T5, T6> : Parameter<T1, T2, T3, T4, T5> 
{ 
    public Parameter(T1 param1, T2 param2, T3 param3, T4 param4, T5 param5, T6 param6) 
     : base(param1, param2, param3, param4, param5) 
    { 
     this.Param6 = param6; 
    } 

    public T6 Param6 { get; set; } 
} 

public class Parameter<T1, T2, T3, T4, T5, T6, T7> : Parameter<T1, T2, T3, T4, T5, T6> 
{ 
    public Parameter(T1 param1, T2 param2, T3 param3, T4 param4, T5 param5, T6 param6, T7 param7) 
     : base(param1, param2, param3, param4, param5, param6) 
    { 
     this.Param7 = param7; 
    } 

    public T7 Param7 { get; set; } 
} 

public class Parameter<T1, T2, T3, T4, T5, T6, T7, T8> : Parameter<T1, T2, T3, T4, T5, T6, T7> 
{ 
    public Parameter(T1 param1, T2 param2, T3 param3, T4 param4, T5 param5, T6 param6, T7 param7, T8 param8) 
     : base(param1, param2, param3, param4, param5, param6, param7) 
    { 
     this.Param8 = param8; 
    } 

    public T8 Param8 { get; set; } 
} 

zgadzam się, że jest ładniejszy mający Param1 niż Item1 jak to sprawia, że ​​korzystanie z bardziej zrozumiałe. Na szczęście udało mi się przenieść do delegatów opartych na reflikach w jednym scenariuszu, w którym potrzebowałem zachowania podczas odczytu/zapisu. W moim przypadku musiałem unikać wprowadzania jakichkolwiek klas do rozwiązania. Mam nadzieję że to pomoże!

Powiązane problemy