为什么我要避免在C#中创建一个MutableTuple <t1,t2,tetc>类?</t1,t2,tetc>

时间:2011-05-10 19:02:03

标签: c# oop c#-4.0 .net-4.0 tuples

我是.NET 4.0 Tuple classes的忠实粉丝。

元组中的所有项都是不可变的。有明显的情况这是有益的(最明显的是当元组用于表示没有声明的临时ValueType时)。

但是,我有一些用例,我可以看到具有setter的元组项目的好处(TRest中的Tuple<T1, T2, T3, T4, T5, T6, T7, TRest> Type参数除外)。鉴于我可以访问源代码和Matt Ellis's article on "Building Tuple",看起来实现这样的MutableTuple<T1,T2,TEtc>非常简单。

微软显然决定让Tuple不可变。 我是否有理由忽视我不应该使用可变的非元组项创建替代实现?

3 个答案:

答案 0 :(得分:24)

在我看来,Tuple类通常只应用于表示短期场景中的数据,并且仅用于内部实现细节。例如,它们在重构期间从私有方法返回多个值时提供了便利。

一旦值成为公共API的一部分,或者更长时间在API中变得更长,我个人认为从可维护性角度来看,使用包含所需属性的自定义类或结构会变得更好,有适当的名字。

因此 - 根据定义,“可变元组”几乎就是为了拥有更长的生命周期而创建的东西 - 如果你要创建它,然后改变它,你就会有效地说该对象存在于某个目的。我建议使用自定义类来保存此数据,因为它在可维护性方面提供了许多优势,包括:

  • 正确命名变量
  • 在包含数据的类中添加验证的能力

第二点,尤其是随着时间的推移变得非常重要 - 如果你要改变数据,你可能必须在某个时候验证它以验证这些值是否合适。如果使用元组,则此验证必须在包含数据的类之外进行,这可能会大大降低代码的可维护性。

答案 1 :(得分:3)

我为Afterthought创建了一个读/写版本。但是,根据社区的API反馈,我选择更改API以使其不必要。但是,我的初始需求与您的类似,具有强类型的方法参数值,并且需要类似于Tuple的行为,即读/写和.NET 3.5兼容。这是我的实现,减去了对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; }
}

我同意Param1比Item1更好,因为它使得使用更容易理解。幸运的是,我能够在需要读/写行为的场景中转移到基于ref的代理。在我的情况下,我不得不避免在解决方案中引入任何类。希望这有帮助!

答案 2 :(得分:1)

Tuple类是不可变的,它与函数式编程概念有关。在这些概念中,您希望所有变量都是不可变的。在此领域中“更改”值的正确方法是使用更改的值创建副本。您可以通过传入原始元组和新值来轻松完成此操作,使用旧值和新值创建一个新元组并返回新元组。这可以确保创建元组的任何代码都可以保证在传递给另一个函数时不会被修改。如果你创建了一个可变类,我就不会把它称为Tuple,因为这违背了传统的期望。

实际上,使用f#之类的东西可能会更好。它具有高阶函数,类型推断和计算表达式,虽然不一定会降低代码的复杂性,但会增加可读性的突飞猛进。