2011-05-10 12 views
21

Soy un gran admirador de .NET 4.0's Tuple classes.¿Por qué debería evitar crear una clase MutableTuple <T1,T2,TEtc> en C#?

Todos los artículos en las Tuplas son inmutables. Claramente hay casos en que esto es beneficioso (más obviamente cuando las Tuplas se usan para representar un ValueType ad hoc para el cual no hay declaración).

Sin embargo, tengo algunos casos de uso en los que pude ver el beneficio de que los artículos de Tuple tengan ajustadores (con la excepción del parámetro de tipo TRest en el Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>). Dado que tengo acceso a la fuente y al Matt Ellis's article on "Building Tuple", parece que sería muy simple implementar un MutableTuple<T1,T2,TEtc>.

Hubo una clara decisión de Microsoft de hacer que el Tuple fuera inmutable. ¿Hay alguna razón por la que no reconozco que no debo crear una implementación alternativa con elementos mudos que no sean tuplas?

+8

¿Por qué no creas una clase pequeña para contener la información que necesitas, completa con propiedades y todo? – Chad

+0

@Chad, porque es más trabajo de lo necesario – smartcaveman

+0

posible duplicado de [¿Por qué los elementos de Tuple son ReadOnly?] (Http://stackoverflow.com/questions/3131400/why-tuples-items-are-readonly) – nawfal

Respuesta

24

En mi opinión, las clases Tuple solo deberían usarse para representar datos en un escenario de corta vida, y solo para detalles de implementación interna. Por ejemplo, proporcionan comodidad al devolver múltiples valores de un método privado durante una refactorización.

Tan pronto como el valor pasa a formar parte de una API pública o se prolonga la vida dentro de una API, personalmente creo que es mucho mejor desde el punto de vista de la mantenibilidad utilizar una clase o estructura personalizada que contenga las propiedades exactas necesidad, con nombres apropiados.

Como tal, una "tupla mutable" sería, por definición, algo creado con la intención de tener un ciclo de vida más largo; si vas a crearlo, luego mutarlo, estás efectivamente diciendo que el objeto existe para un propósito. Yo recomendaría una clase personalizada para mantener estos datos en ese punto, ya que ofrece muchas ventajas en términos de facilidad de mantenimiento, incluyendo:

  • denominación adecuada de las variables
  • La posibilidad de agregar validación dentro de la clase que contiene los datos

El segundo punto, especialmente, se vuelve muy importante con el tiempo: si vas a mutar datos, probablemente tendrás que validarlo en algún momento para verificar que los valores sean apropiados. Si utiliza una tupla, esta validación debería tener lugar fuera de la clase que contiene los datos, lo que probablemente reducirá drásticamente la capacidad de mantenimiento de su código.

+0

en realidad, mi caso de uso no implica un "ciclo de vida más largo". El ciclo de vida previsto para mi caso de uso está dentro del alcance de un método de extensión. Considere las implicaciones para una API Fluent usando extensiones: (1) 'Func , TResult> Tupled (este Func func)', y (2) 'TResult InvokeWithContract (este Func func, Func validatePreCondition, Acción resolvePreConditionFail, Func validatePostCondition, Acción resolvePostConditionFail, T args) donde T: IStructuralEquatable, IStructuralComparable' – smartcaveman

+0

Aunque reconozco que esto podría lograrse con un 'Func 'delegar en su lugar, significaría que no podría usar un parámetro' in' genérico, y también que mi código no sería tan breve. ¿Todavía crees que una MutableTuple es una mala decisión en esta circunstancia particular? – smartcaveman

+0

@smartcaveman: Personalmente todavía lo trataría inmutable, y, si tiene que cambiarlo, copie los miembros en una nueva tupla. Dicho esto, para una API como esta, probablemente usaría una clase personalizada, especialmente si esto se va a usar en una API pública ... –

1

La clase Tuple es inmutable y se relaciona con los conceptos de programación funcional. Dentro de esos conceptos, usted espera que todas sus variables sean inmutables. La forma correcta de "cambiar" valores en este ámbito es crear una copia con los valores modificados. Puede lograr esto fácilmente pasando la tupla original y el nuevo valor, crear una nueva tupla con los valores anteriores más la nueva y devolver la nueva tupla. Esto garantiza que cualquier código que crea una tupla puede garantizar que no se modificará cuando se pase a otra función. Si creaste una clase mutable, no la llamaría Tuple ya que va en contra de las expectativas convencionales.

En realidad, podría tener más suerte al usar algo como f # para su implementación. Tiene funciones de orden superior, tipo de inferencia y expresiones computacionales que, aunque no necesariamente reducen la complejidad de su código, aumentarían la legibilidad a pasos agigantados.

3

Creé una versión de lectura/escritura para Afterthought. Sin embargo, en función de los comentarios de la API de la comunidad, elegí cambiar la API para que no sea necesaria. Sin embargo, mi necesidad inicial era similar a la tuya, tenía valores de parámetros fuertemente tipados para los métodos, y quería un comportamiento tipo Tuple que fuera de lectura/escritura y también compatible con .NET 3.5. Aquí está mi aplicación, menos apoyo a 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; } 
} 

Estoy de acuerdo que es más agradable tener que Param1 Elemento1 ya que hace que el uso sea más comprensible. Afortunadamente, pude pasar a delegados basados ​​en referencias para el único escenario en el que necesitaba un comportamiento de lectura/escritura. En mi caso, tuve que evitar introducir clases en la solución. ¡Espero que esto ayude!

Cuestiones relacionadas