Question

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

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?

 21  4585  21
1 Jan 1970

Solution

 24

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.

2011-05-10

Solution

 3

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!

2011-05-16