侧边栏壁纸
博主头像
komi

Bona Fides

  • 累计撰写 9 篇文章
  • 累计创建 17 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

C#中的struct与ref

komi
2023-10-31 / 0 评论 / 0 点赞 / 464 阅读 / 2,722 字

struct?

Structs are similar to classes in that they represent data structures that can contain data members and function members. However, unlike classes, structs are value types and do not require heap allocation. A variable of a struct type directly contains the data of the struct, whereas a variable of a class type contains a reference to the data, the latter known as an object.

struct和我们平时用到的class非常相似 但是最大的不同是 struct属于值类型 所以并不会在上分配空间 而是在上 而从GC的角度来看 当方法执行结束后CLR会直接回收对应的资源(内存占用)

var c = new claim("admin", 12);
var anotherClaim = c;
anotherClaim.claimName = "sans";
anotherClaim.ttl = 1;

Console.WriteLine(c.ttl);
Console.WriteLine(anotherClaim.ttl);

var claimClass = new claimClass("admin", 12);

var dd = claimClass;
dd.claimName = "john doe";
dd.ttl = 1;

Console.WriteLine(claimClass.ttl);
Console.WriteLine(dd.ttl);

public struct claim
{
    public claim(string name, int ttl)
    {
        claimName = name;
        this.ttl = ttl;
    }

    public string claimName { get; set; }

    public int ttl { get; set; }
}

public class claimClass
{
    public claimClass()
    {
    }

    public claimClass(string name, int ttl)
    {
        claimName = name;
        this.ttl = ttl;
    }

    public string claimName { get; set; }

    public int ttl { get; set; }
}

运行以上代码会发现 cttlanotherClaim中修改的ttl不同 可以说这里的复制是做了深拷贝的 而claimClassddttl都为1

struct相对class的一些不足

  1. struct无法继承父类(因为C#单继承机制 已经默认继承了System.ValueType) 同时也不能作为其他类的基类(因为struct默认是sealed) 不过可以实现接口
  2. struct中不允许实例属性或字段包含初始值设定项 不够struct允许静态属性或字段包含初始值设定项
public struct Coords
{
    public double x = 4; //错误, 结构体中初始化器不允许实例字段设定初始值
    public static double y = 5; // 正确
    public static double z { get; set; } = 6; // 正确
}
  1. 不可以直接声明无参构造函数(C#10之前) 考虑到是值类型 它没有默认值 也就是非null的 这样无参构造函数就是不必要的 https://stackoverflow.com/a/334683 不过在C#10后又将这个限制去掉了(https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/parameterless-struct-constructors#constructors)
  2. 不能够频繁地进行拆箱以及装箱操作 这会对性能大打折扣 所以要求struct一定是immutable(不可变的)

struct的实际应用

熟悉C#的朋友一定记得有这样的一个类型 Tuple 元组类型 而ValueTuple更像是对Tuple的进一步升级版

Tuple

    [Serializable]
    public class Tuple<T1, T2> : IStructuralEquatable, IStructuralComparable, IComparable, ITuple {

        private readonly T1 m_Item1;
        private readonly T2 m_Item2;

        public T1 Item1 { get { return m_Item1; } }
        public T2 Item2 { get { return m_Item2; } }

        public Tuple(T1 item1, T2 item2) {
            m_Item1 = item1;
            m_Item2 = item2;
        }

        public override Boolean Equals(Object obj) {
            return ((IStructuralEquatable) this).Equals(obj, EqualityComparer<Object>.Default);;
        }

        Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer) {
            if (other == null) return false;

            Tuple<T1, T2> objTuple = other as Tuple<T1, T2>;

            if (objTuple == null) {
                return false;
            }

            return comparer.Equals(m_Item1, objTuple.m_Item1) && comparer.Equals(m_Item2, objTuple.m_Item2);
        }

        Int32 IComparable.CompareTo(Object obj) {
            return ((IStructuralComparable) this).CompareTo(obj, Comparer<Object>.Default);
        }

        Int32 IStructuralComparable.CompareTo(Object other, IComparer comparer) {
            if (other == null) return 1;

            Tuple<T1, T2> objTuple = other as Tuple<T1, T2>;

            if (objTuple == null) {
                throw new ArgumentException(Environment.GetResourceString("ArgumentException_TupleIncorrectType", this.GetType().ToString()), "other");
            }

            int c = 0;

            c = comparer.Compare(m_Item1, objTuple.m_Item1);

            if (c != 0) return c;

            return comparer.Compare(m_Item2, objTuple.m_Item2);
        }

        public override int GetHashCode() {
            return ((IStructuralEquatable) this).GetHashCode(EqualityComparer<Object>.Default);
        }

        Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer) {
            return Tuple.CombineHashCodes(comparer.GetHashCode(m_Item1), comparer.GetHashCode(m_Item2));
        }

        Int32 ITuple.GetHashCode(IEqualityComparer comparer) {
            return ((IStructuralEquatable) this).GetHashCode(comparer);
        }
        public override string ToString() {
            StringBuilder sb = new StringBuilder();
            sb.Append("(");
            return ((ITuple)this).ToString(sb);
        }

        string ITuple.ToString(StringBuilder sb) {
            sb.Append(m_Item1);
            sb.Append(", ");
            sb.Append(m_Item2);
            sb.Append(")");
            return sb.ToString();
        }

        int ITuple.Size {
            get {
                return 2;
            }
        }
    }

ValueTuple

    /// <summary>
    /// Represents a 2-tuple, or pair, as a value type.
    /// </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>
    [Serializable]
    [StructLayout(LayoutKind.Auto)]
    public struct ValueTuple<T1, T2>
        : IEquatable<ValueTuple<T1, T2>>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable<ValueTuple<T1, T2>>, IValueTupleInternal, ITuple
    {
        /// <summary>
        /// The current <see cref="ValueTuple{T1, T2}"/> instance's first component.
        /// </summary>
        public T1 Item1;

        /// <summary>
        /// The current <see cref="ValueTuple{T1, T2}"/> instance's first component.
        /// </summary>
        public T2 Item2;

        /// <summary>
        /// Initializes a new instance of the <see cref="ValueTuple{T1, T2}"/> value type.
        /// </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 ValueTuple(T1 item1, T2 item2)
        {
            Item1 = item1;
            Item2 = item2;
        }

        /// <summary>
        /// Returns a value that indicates whether the current <see cref="ValueTuple{T1, T2}"/> instance is equal to a specified object.
        /// </summary>
        /// <param name="obj">The object to compare with this instance.</param>
        /// <returns><see langword="true"/> if the current instance is equal to the specified object; otherwise, <see langword="false"/>.</returns>
        ///
        /// <remarks>
        /// The <paramref name="obj"/> parameter is considered to be equal to the current instance under the following conditions:
        /// <list type="bullet">
        ///     <item><description>It is a <see cref="ValueTuple{T1, T2}"/> value type.</description></item>
        ///     <item><description>Its components are of the same types as those of the current instance.</description></item>
        ///     <item><description>Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component.</description></item>
        /// </list>
        /// </remarks>
        public override bool Equals(object obj)
        {
            return obj is ValueTuple<T1, T2> && Equals((ValueTuple<T1, T2>)obj);
        }

        /// <summary>
        /// Returns a value that indicates whether the current <see cref="ValueTuple{T1, T2}"/> instance is equal to a specified <see cref="ValueTuple{T1, T2}"/>.
        /// </summary>
        /// <param name="other">The tuple to compare with this instance.</param>
        /// <returns><see langword="true"/> if the current instance is equal to the specified tuple; otherwise, <see langword="false"/>.</returns>
        /// <remarks>
        /// The <paramref name="other"/> parameter is considered to be equal to the current instance if each of its fields
        /// are equal to that of the current instance, using the default comparer for that field's type.
        /// </remarks>
        public bool Equals(ValueTuple<T1, T2> other)
        {
            return EqualityComparer<T1>.Default.Equals(Item1, other.Item1)
                && EqualityComparer<T2>.Default.Equals(Item2, other.Item2);
        }

        /// <summary>
        /// Returns a value that indicates whether the current <see cref="ValueTuple{T1, T2}"/> instance is equal to a specified object based on a specified comparison method.
        /// </summary>
        /// <param name="other">The object to compare with this instance.</param>
        /// <param name="comparer">An object that defines the method to use to evaluate whether the two objects are equal.</param>
        /// <returns><see langword="true"/> if the current instance is equal to the specified object; otherwise, <see langword="false"/>.</returns>
        ///
        /// <remarks>
        /// This member is an explicit interface member implementation. It can be used only when the
        ///  <see cref="ValueTuple{T1, T2}"/> instance is cast to an <see cref="IStructuralEquatable"/> interface.
        ///
        /// The <see cref="IEqualityComparer.Equals"/> implementation is called only if <c>other</c> is not <see langword="null"/>,
        ///  and if it can be successfully cast (in C#) or converted (in Visual Basic) to a <see cref="ValueTuple{T1, T2}"/>
        ///  whose components are of the same types as those of the current instance. The IStructuralEquatable.Equals(Object, IEqualityComparer) method
        ///  first passes the <see cref="Item1"/> values of the <see cref="ValueTuple{T1, T2}"/> objects to be compared to the
        ///  <see cref="IEqualityComparer.Equals"/> implementation. If this method call returns <see langword="true"/>, the method is
        ///  called again and passed the <see cref="Item2"/> values of the two <see cref="ValueTuple{T1, T2}"/> instances.
        /// </remarks>
        bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer)
        {
            if (other == null || !(other is ValueTuple<T1, T2>)) return false;

            var objTuple = (ValueTuple<T1, T2>)other;

            return comparer.Equals(Item1, objTuple.Item1)
                && comparer.Equals(Item2, objTuple.Item2);
        }

        int IComparable.CompareTo(object other)
        {
            if (other == null) return 1;

            if (!(other is ValueTuple<T1, T2>))
            {
                throw new ArgumentException();
            }

            return CompareTo((ValueTuple<T1, T2>)other);
        }

        /// <summary>Compares this instance to a specified instance and returns an indication of their relative values.</summary>
        /// <param name="other">An instance to compare.</param>
        /// <returns>
        /// A signed number indicating the relative values of this instance and <paramref name="other"/>.
        /// Returns less than zero if this instance is less than <paramref name="other"/>, zero if this
        /// instance is equal to <paramref name="other"/>, and greater than zero if this instance is greater 
        /// than <paramref name="other"/>.
        /// </returns>
        public int CompareTo(ValueTuple<T1, T2> other)
        {
            int c = Comparer<T1>.Default.Compare(Item1, other.Item1);
            if (c != 0) return c;

            return Comparer<T2>.Default.Compare(Item2, other.Item2);
        }

        int IStructuralComparable.CompareTo(object other, IComparer comparer)
        {
            if (other == null) return 1;

            if (!(other is ValueTuple<T1, T2>))
            {
                throw new ArgumentException();
            }

            var objTuple = (ValueTuple<T1, T2>)other;

            int c = comparer.Compare(Item1, objTuple.Item1);
            if (c != 0) return c;

            return comparer.Compare(Item2, objTuple.Item2);
        }

        /// <summary>
        /// Returns the hash code for the current <see cref="ValueTuple{T1, T2}"/> instance.
        /// </summary>
        /// <returns>A 32-bit signed integer hash code.</returns>
        public override int GetHashCode()
        {
            return ValueTuple.CombineHashCodes(Item1?.GetHashCode() ?? 0,
                                               Item2?.GetHashCode() ?? 0);
        }

        int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
        {
            return GetHashCodeCore(comparer);
        }

        private int GetHashCodeCore(IEqualityComparer comparer)
        {
            return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1),
                                               comparer.GetHashCode(Item2));
        }

        int IValueTupleInternal.GetHashCode(IEqualityComparer comparer)
        {
            return GetHashCodeCore(comparer);
        }

        /// <summary>
        /// Returns a string that represents the value of this <see cref="ValueTuple{T1, T2}"/> instance.
        /// </summary>
        /// <returns>The string representation of this <see cref="ValueTuple{T1, T2}"/> instance.</returns>
        /// <remarks>
        /// The string returned by this method takes the form <c>(Item1, Item2)</c>,
        /// where <c>Item1</c> and <c>Item2</c> represent the values of the <see cref="Item1"/>
        /// and <see cref="Item2"/> fields. If either field value is <see langword="null"/>,
        /// it is represented as <see cref="String.Empty"/>.
        /// </remarks>
        public override string ToString()
        {
            return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ")";
        }

        string IValueTupleInternal.ToStringEnd()
        {
            return Item1?.ToString() + ", " + Item2?.ToString() + ")";
        }

        /// <summary>
        /// The number of positions in this data structure.
        /// </summary>
        int ITuple.Length => 2;

        /// <summary>
        /// Get the element at position <param name="index"/>.
        /// </summary>
        object ITuple.this[int index]
        {
            get
            {
                switch (index)
                {
                    case 0:
                        return Item1;
                    case 1:
                        return Item2;
                    default:
                        throw new IndexOutOfRangeException();
                }
            }
        }
    }

可以看出ValueTuple作为struct和作为class的Tuple相比 更多的将方法实现侧重于预留的字段
比如Equals方法
Tuple需要把传入的值进行一次as类型转换再用Object的比较器进行比较
而ValueTuple会预先判断传入值的类型 再根据字段对应的属性创建对应泛型的默认比较器来逐个比较每个字段
而ValueTuple也是将Tuple中的只读字段改为了可读可写

说一千道一万 这和ref有什么关系?

ref struct其实在C#7中就可以使用但仅仅是用于限制struct本身频繁装箱带来的内存分配问题

什么时候会触发装箱操作?

  1. struct转换为引用类型的时候 例如接口,object等
  2. 直接调用ToString方法 而struct本身没有重写ToString方法

但是对于struct内的字段并不支持直接使用ref关键字限制 而从C#11开始(.Net7)便支持了在字段上使用ref关键字

public ref struct StructureData
{
    ref readonly int field1;
    readonly ref int field2;
    readonly ref readonly int field3;

    public StructureData(int[] figures)
    {
        field1 = ref figures[0]; // 成功 赋值0
        field2 = figures[1]; // 编译通过 运行时异常 无法赋值
        field2 = ref figures[1]; // 成功 赋值1
        field3 = figures[2]; // 编译失败
        field3 = ref figures[2]; // 成功 赋值2
    }

    void takePlace(int[] figures)
    {
        field3 = figures[2]; // 编译失败
        field3 = ref figures[2]; // 编译失败
    }
}

总而言之 这里的ref限制主要是对于参数上的赋值约束

0

评论区