为什么我应该避免创建 MutableTuple C# 中的类?

发布于 2024-11-05 22:47:30 字数 587 浏览 0 评论 0 原文

我是 .NET 4.0 的 元组类 的忠实粉丝。

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

但是,在一些用例中,我可以看到元组项目具有设置器的好处(元组 TRest 类型参数除外, T5、T6、T7、Trest>)。鉴于我可以访问源代码和 Matt Ellis 关于“构建元组”的文章< /a>,实现这样一个 MutableTuple 似乎非常简单。

微软显然决定让 Tuple 不可变。 是否有一个原因让我忽略了我不应该使用可变的非元组项创建替代实现?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

如梦 2024-11-12 22:47:30

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

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

因此,根据定义,“可变元组”几乎是为了拥有更长的生命周期而创建的东西 - 如果您要创建它,然后对其进行变异,那么您实际上是在说该对象存在是有目的的。我建议使用自定义类来保存这些数据,因为它在可维护性方面提供了许多优点,包括:

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

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

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.

昨迟人 2024-11-12 22:47:30

我为 Afterthought 创建了一个读/写版本。然而,根据社区的 API 反馈,我选择更改 API,使其变得不必要。但是,我最初的需求与您的类似,具有强类型的方法参数值,并希望具有读/写且与 .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 更好,因为它使用法更容易理解。幸运的是,我能够在需要读/写行为的一种场景中转向基于引用的委托。就我而言,我必须避免在解决方案中引入任何类。希望这有帮助!

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!

薄暮涼年 2024-11-12 22:47:30

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

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文