Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why should I avoid creating a MutableTuple<T1,T2,TEtc> class in C#?

Tags:

I am a big fan of .NET 4.0's Tuple classes.

All the items in the Tuples are immutable. There are clearly cases where this is beneficial (most obviously when Tuples are used to represent an ad hoc ValueType for which there is no declaration).

However, I have some use cases where I could see the benefit to a Tuple's items having setters (with the exception of the TRest Type parameter in the Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>). Given that I have access to the source and to Matt Ellis's article on "Building Tuple", it seems like it would be pretty simple to implement such a MutableTuple<T1,T2,TEtc>.

There was clearly a decision by Microsoft to make the Tuple immutable. Is there a reason that I am overlooking that I shouldn't create an alternate implementation with mutable non-tuple items?

like image 545
smartcaveman Avatar asked May 10 '11 19:05

smartcaveman


3 Answers

In my opinion, the Tuple classes should typically only be used to represent data in a short lived scenario, and only for internal implementation details. For example, they provide convenience when returning multiple values from a private method during a refactoring.

As soon as the value becomes part of a public API, or longer becomes longer lived within an API, I personally feel that it becomes much better from a maintainability standpoint to use a custom class or struct which contains the exact properties you need, with appropriate names.

As such - a "mutable tuple" would pretty much, by definition, be something that's created with the intent of having a longer lifecycle - if you're going to create it, then later mutate it, you're effectively saying that the object exists for a purpose. I would recommend a custom class to hold this data at that point, as it provides many advantages in terms of maintainability, including:

  • Proper naming of variables
  • The ability to add validation within the class containing the data

The second point, especially, becomes very important over time - if you're going to be mutating data, you'll likely have to be validating it at some point to verify that the values are appropriate. If you use a tuple, this validation would have to occur outside of the class containing the data, which is likely to dramatically reduce the maintainability of your code.

like image 133
Reed Copsey Avatar answered May 23 '23 03:05

Reed Copsey


I created a read/write version for Afterthought. However, based on API feedback from the community I elected to change the API to make it unnecessary. However, my initial need was similar to yours, having strongly-typed parameter values for methods, and wanting Tuple-like behavior that was read/write and also .NET 3.5 compatible. Here is my implementation, minus support for 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; }
}

I agree that it is nicer having Param1 than Item1 as it makes the usage more understandable. I was fortunately able to move to ref-based delegates for the one scenario where I needed read/write behavior. In my case I had to avoid introducing any classes into the solution. Hope this helps!

like image 27
Jamie Thomas Avatar answered May 23 '23 02:05

Jamie Thomas


The Tuple class being immutable relates back to functional programming concepts. Within those concepts you expect all your variables to be immutable. The correct way to "change" values in this realm is to create a copy with the changed values. You can easily accomplish this by passing in the original tuple and the new value, create a new tuple with the old values plus the new one and return the new tuple. This ensures that any code which creates a tuple can guarantee that it will not be modified when passed to another function. If you created a mutable class, I would not call it Tuple as that goes against conventional expectations.

You might actually have better luck using something like f# for your implementation. It has higher-order functions, type inference and computational expressions which, while not necessarily reducing the complexity of your code, would increase the readability by leaps and bounds.

like image 34
Charles Lambert Avatar answered May 23 '23 01:05

Charles Lambert