为什么元组的项是只读的?

58

我在考虑使用Tuple类来存储程序中需要的2个整数信息(起始地址和结束地址)。

但是我发现Tuple中的项目是只读的,因此如果我需要为一个项目设置值,我需要重新实例化一个 Tuple。

这个设计决策背后的原因是什么?


1
我认为在面向对象编程中,元组通常只是开发人员描述数据结构的懒惰方式。然而,在函数式编程中,它是金牛犊。我并不是说其中任何一种好或坏,有时我也很懒。只是对于不同的用例可能有不同的最佳实践。 - Robert Cutajar
2
在学习了许多高级元组技术并发现元组是只读的之后,@Rbjz终于意识到,如果你在许多地方使用该结构,则创建具有2个属性的自定义类是更好的方法。 - jw_
4个回答

56

元组(tuples)起源于函数式编程。在(纯)函数式编程中,一切都是不可变的 - 一个特定的变量在所有时间内只有一个定义,就像数学中一样。当将函数式风格集成到C#/.NET时,.NET设计师明智地遵循了相同的原则,尽管它最终是一种主要的命令式(混合?)语言。

注意: 虽然我怀疑元组是不可变的这一事实并没有让你的任务更加困难,但你可能也想使用匿名类型(或者只是简单的结构体)。


10
CLR设计者,而不是C#设计者。.NET 4中的System.Tuple也被F#隐式使用。 - Julien Lebosquain
4
有关 Tuple 类型的决定更多是由 BCL 开发人员做出的,而不是 C# 设计师 - 他们决定将 F# 加入 .Net 语言系列,并采用统一的 Tuple 类型。 - Damien_The_Unbeliever
1
啊,不可避免的学究气。此外,在.NET 4.0之前,F#就使用了自己的元组类型——这种说法有点无关紧要。 - Noldorin
1
@Noldorin "CLR可以支持可变元组"。这是一个我很想看到例子的声明:您是否意味着CLR中有一些特定使用内置元组设施的东西,围绕它可以构建一个读写元组;或者,您只是在发表广泛的声明,即您可以实现自己的可读写元组版本?这不是一个修辞问题,并且没有讽刺的次文本;我真的很想知道您的答案。 - BillW
1
对于大多数使用场景而言,如果 Tuple<A,B,C,...> 是一个公开字段结构体,则可以实现最佳的语义和性能。任何大小的公开字段结构体,如果被复制不到三次,则性能优于类;非常小的结构体无论被复制多少次都优于类,除非结构体很大,否则它们将优于类,除非被复制多次。唯一需要注意的是,如果一个结构体需要被装箱一次,那么类会比它更快,如果一个结构体需要经常被装箱,那么类会更胜一筹。 - supercat
显示剩余6条评论

2

我不知道为什么没有这样的东西,但这正是我想要使用的。

namespace System
{
    /// <summary>
    /// Helper so we can call some tuple methods recursively without knowing the underlying types.
    /// </summary>
    internal interface IWTuple
    {
        string ToString(StringBuilder sb);
        int GetHashCode(IEqualityComparer comparer);
        int Size { get; }
    }

    /// <summary>
    /// Represents a writable 2-tuple, or pair.
    /// </summary>
    /// <typeparam name="T1">The type of the tuple's first component.</typeparam>
    /// <typeparam name="T2">The type of the tuple's second component.</typeparam>
    public class WTuple<T1, T2> : IStructuralEquatable, IStructuralComparable, IComparable, IWTuple
    {
        private T1 _item1;
        private T2 _item2;

        #region ImplementedInterfaces
        Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
        {
            return comparer.GetHashCode(_item1);
        }
        Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer) {
            if (other == null) return false;
            WTuple<T1, T2> objTuple = other as WTuple<T1, T2>;//Tuple<t1, t2=""> objTuple = other as Tuple<t1, t2="">;
            if (objTuple == null) {
                return false;
            }
            return comparer.Equals(_item1, objTuple._item1) && comparer.Equals(_item2, objTuple._item2);
        }
        Int32 IStructuralComparable.CompareTo(Object other, IComparer comparer)
        {
            if (other == null) return 1;
            WTuple<T1, T2> objTuple = other as WTuple<T1, T2>;//Tuple<t1, t2=""> objTuple = other as Tuple<t1, t2="">;
            if (objTuple == null)
            {
                throw new ArgumentException("ArgumentException_TupleIncorrectType", "other");//ArgumentException(Environment.GetResourceString("ArgumentException_TupleIncorrectType", this.GetType().ToString()), "other");
            }
            int c = 0;
            c = comparer.Compare(_item1, objTuple._item1);
            if (c != 0) return c;
            return comparer.Compare(_item2, objTuple._item2);
        }
        Int32 IComparable.CompareTo(Object obj)
        {
            return ((IStructuralComparable)this).CompareTo(obj, Comparer<object>.Default);
        }
        Int32 IWTuple.GetHashCode(IEqualityComparer comparer)
        {
            return ((IStructuralEquatable)this).GetHashCode(comparer);
        }
        string IWTuple.ToString(StringBuilder sb)
        {
            sb.Append(_item1);
            sb.Append(", ");
            sb.Append(_item2);
            sb.Append(")");
            return sb.ToString();
        }
        int IWTuple.Size
        {
            get { return 2; }
        }
        #endregion

        #region WTuple
        /// <summary>
        /// Initializes a new instance of the System.WTuple&lt;T1,T2&gt; class.
        /// </summary>
        /// <param name="item1">The value of the tuple's first component.</param>
        /// <param name="item2">The value of the tuple's second component.</param>
        public WTuple(T1 item1, T2 item2)
        {
            _item1 = item1;
            _item2 = item2;
        }
        /// <summary>
        /// Gets or sets the value of the current System.WTuple&lt;T1,T2&gt; object's first component.
        /// </summary>
        public T1 Item1
        {
            get { return _item1; }
            set { _item1 = value; }
        }
        /// <summary>
        /// Gets or sets the value of the current System.WTuple&lt;T1,T2&gt; object's second component.
        /// </summary>
        public T2 Item2
        {
            get { return _item2; }
            set { _item2 = value; }
        }
        /// <summary>
        /// Returns a value that indicates whether the current System.WTuple&lt;T1,T2&gt; object
        /// is equal to a specified object.
        /// </summary>
        /// <param name="obj">The object to compare with this instance.</param>
        /// <returns>true if the current instance is equal to the specified object; otherwise,
        /// false.</returns>
        public override Boolean Equals(Object obj)
        {
            return ((IStructuralEquatable)this).Equals(obj, EqualityComparer<object>.Default);
        }
        /// <summary>
        /// Returns the hash code for the current System.WTuple&lt;T1,T2&gt; object.
        /// </summary>
        /// <returns>A 32-bit signed integer hash code.</returns>
        public override int GetHashCode()
        {
            return ((IStructuralEquatable)this).GetHashCode(EqualityComparer<object>.Default);
        }
        /// <summary>
        /// Returns a string that represents the value of this System.WTuple&lt;T1,T2&gt; instance.
        /// </summary>
        /// <returns>The string representation of this System.WTuple&lt;T1,T2&gt; object.</returns>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("(");
            return ((IWTuple)this).ToString(sb);
        }
        #endregion
    }
}

1
这段代码实际上并没有使用其继承的结构体,而是在构造函数中设置了基础元组的值...它们保持不变且不可更改。在属性前使用“new”运算符是不必要的,也没有任何效果。更改“Item1”的setter代码如下,并多次设置该值以查看基类中的值永远不会被更改:set { _item1 = value; Console.WriteLine(_item1.ToString()); Console.WriteLine(base.Item1.ToString()); } - BillW
谢谢您提到这个。很抱歉,实际上我从来没有测试过重写的函数,只使用了Item1和Item2(这显然有效)。我发现无论如何都不可能更改Tuple<T1,T2>中的条目,因为它使用readonly属性,如我在这里看到的那样:http://reflector.webtropy.com/default.aspx/4@0/4@0/untmp/DEVDIV_TFS/Dev10/Releases/RTMRel/ndp/clr/src/BCL/System/Tuple@cs/1305376/Tuple@cs 但是,我已经更新了代码,使其以类似于Tuple<T1,T2>的方式实现接口。 - xamid

1

请注意, ValueTuple 不会将其成员视为属性而是字段。这可能会导致问题。更多信息请参见此处 - ruffin

-3

你只得到了ItemX属性的getter,没错,但我找到了一种方法,首先实例化一个带有空值的元组,然后在之后填充它们。

如果你做这样的事情:

Dictionary <string, Tuple<string, string>> mydic = new  Dictionary<string,Tuple<string,string>>(); 
Tuple<string, string> tplTemp = new Tuple<string, string>("", "");
 mydic.TryGetValue("akey", out tplTemp);

传递作为输出参数的tplTemp将从集合中获取其2个项目值。 所以这是一种方法,如果有帮助的话。

5
这不是一种填充Tuple值的方法。你所做的只是创建了第二个Tuple并将tplTemp赋值给它的值。你的代码等同于只是这样做:tplTemp = new Tuple<string, string>("some", "values"); - gerrard00

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接