代码之家  ›  专栏  ›  技术社区  ›  Garry Shutler

在不可变对象和值对象的结构之间进行选择

  •  21
  • Garry Shutler  · 技术社区  · 17 年前

    如何在将值对象(规范示例是地址)实现为不可变对象或结构之间进行选择?

    在性能、语义或其他方面,选择其中一个是否有好处?

    11 回复  |  直到 11 年前
        1
  •  13
  •   Konrad Rudolph    17 年前

    如何在将值对象(规范示例是地址)实现为不可变对象或结构之间进行选择?

    我认为你的选择是错误的。不变的对象和结构不是对立的,也不是唯一的选项。相反,您有四种选择:

    • 等级
      • 易变的
      • 不变的
    • 结构
      • 易变的
      • 不变的

    我认为在.NET中,默认选择应该是 可变类 代表 逻辑 和一个 不可变类 代表一个 实体 .实际上,如果可行的话,我甚至倾向于选择不可变的类来实现逻辑。应为模拟值语义的小型类型保留结构,例如自定义 Date A型 Complex 编号类型类似的实体。这里的重点是 小的 因为您不想复制大量的数据块,而通过引用进行间接寻址实际上是很便宜的(所以使用结构不会获得太多好处)。我倾向于做结构 总是 不可变(目前我想不出一个例外)。因为这最符合内在值类型的语义,我发现这是一个很好的规则。

        2
  •  15
  •   Dan Herbert    17 年前

    有几点需要考虑:

    结构在堆栈上分配(通常)。它是一种值类型,因此如果数据太大,则在方法之间传递数据可能会很昂贵。

    在堆上分配了一个类。它是一个引用类型,因此通过方法传递对象并没有那么昂贵。

    通常,我对不可变的对象使用结构,这些对象不是很大。我只在数据量有限或希望不可变时使用它们。一个例子是 DateTime 结构。我喜欢认为,如果我的物体不像 日期时间 ,它可能不值得用作结构。另外,如果我的对象作为值类型(也像 日期时间 ,则用作结构可能不太有用。但不变性是关键。另外,我想强调结构是 默认情况下不可变。你必须通过设计使它们不变。

    在我遇到的99%的情况下,使用类是正确的。我发现自己不经常需要不可变的类。在大多数情况下,我更自然地认为类是可变的。

        3
  •  14
  •   Community Mohan Dere    8 年前

    我喜欢用思维实验:

    当只调用空构造函数时,此对象是否有意义?

    编辑在 Richard E 的要求

    很好的利用 struct 是包装基元并将其范围限定到有效范围。

    例如,概率的有效范围是0-1。使用十进制来表示这一点很容易出错,并且需要在每个使用点进行验证。

    相反,您可以用验证和其他有用的操作包装一个原语。这通过了思想实验,因为大多数原语都有一个自然的0状态。

    下面是一个示例用法 结构 表示概率:

    public struct Probability : IEquatable<Probability>, IComparable<Probability>
    {
        public static bool operator ==(Probability x, Probability y)
        {
            return x.Equals(y);
        }
    
        public static bool operator !=(Probability x, Probability y)
        {
            return !(x == y);
        }
    
        public static bool operator >(Probability x, Probability y)
        {
            return x.CompareTo(y) > 0;
        }
    
        public static bool operator <(Probability x, Probability y)
        {
            return x.CompareTo(y) < 0;
        }
    
        public static Probability operator +(Probability x, Probability y)
        {
            return new Probability(x._value + y._value);
        }
    
        public static Probability operator -(Probability x, Probability y)
        {
            return new Probability(x._value - y._value);
        }
    
        private decimal _value;
    
        public Probability(decimal value) : this()
        {
            if(value < 0 || value > 1)
            {
                throw new ArgumentOutOfRangeException("value");
            }
    
            _value = value;
        }
    
        public override bool Equals(object obj)
        {
            return obj is Probability && Equals((Probability) obj);
        }
    
        public override int GetHashCode()
        {
            return _value.GetHashCode();
        }
    
        public override string ToString()
        {
            return (_value * 100).ToString() + "%";
        }
    
        public bool Equals(Probability other)
        {
            return other._value.Equals(_value);
        }
    
        public int CompareTo(Probability other)
        {
            return _value.CompareTo(other._value);
        }
    
        public decimal ToDouble()
        {
            return _value;
        }
    
        public decimal WeightOutcome(double outcome)
        {
            return _value * outcome;
        }
    }
    
        4
  •  6
  •   peterchen    17 年前

    因素:结构,记忆需求,拳击。

    通常,结构的构造函数限制-没有显式的无参数构造函数,没有 base 构造-决定是否应该使用结构。例如,如果无参数构造函数应该 将成员初始化为默认值,使用不可变对象。

    如果您仍然可以在这两者之间进行选择,请决定内存需求。小项目应该存储在结构中,特别是在需要许多实例的情况下。

    当实例被装箱(例如,为匿名函数捕获或存储在非通用容器中)时,您甚至开始为装箱额外付费。


    什么是“小”,什么是“多”?

    在32位系统上,对象的开销是(IIRC)8字节。注意,对于几百个实例,这可能已经决定了一个内部循环是否在缓存中完全运行,或者调用GC。如果您期望有数万个实例,这可能是运行与爬网之间的区别。

    从这个POV来看,使用结构并不是一个过早的优化。


    所以,根据经验法则 :

    如果大多数实例都被装箱,请使用不可变的对象。
    否则,对于小对象,只有在结构构造会导致尴尬的接口时才使用不可变对象。 您预计不会超过数千个实例。

        5
  •  2
  •   Richard Ev    17 年前

    在今天的世界中(我认为是C 3.5),我不认为需要结构(编辑:除了在一些特定的场景中)。

    支持结构的论据似乎主要是基于感知到的性能优势。我想看看一些基准(复制现实场景)来说明这一点。

    将结构用于“轻量级”数据结构的概念对我来说似乎太主观了。何时数据不再是轻量级的?另外,当向使用结构的代码添加功能时,您什么时候决定将该类型更改为类?

    就我个人而言,我不记得上次在C中使用结构时的情景。

    编辑

    我建议出于性能原因在C中使用结构显然是过早优化的一个例子。 *

    *除非对应用程序进行了性能分析,并且将类的使用确定为性能瓶颈

    编辑2

    MSDN 国家:

    结构类型适用于 表示轻量级对象 作为点、矩形和颜色。 尽管可以表示 点作为类,结构更重要 在某些情况下是有效的。为了 例如,如果声明 1000点对象,分配 用于引用每个的附加内存 对象。在这种情况下,结构是 便宜些。

    除非你需要参考类型 语义,一个更小的类 超过16个字节可能更有效 由系统作为结构处理。

        6
  •  2
  •   Gregory A Beamer    17 年前

    一般来说,我不会为业务对象推荐结构。虽然通过朝这个方向前进可能会获得少量的性能,但当您在堆栈上运行时,最终会以某些方式限制自己,在某些情况下,默认的构造函数可能是一个问题。

    我会说,当你有向公众发布的软件时,这是更为必要的。

    对于简单类型,结构很好,这就是为什么您看到Microsoft对大多数数据类型使用结构的原因。同样,结构对于堆栈上有意义的对象也很好。其中一个答案中提到的点结构就是一个很好的例子。

    我怎么决定?我通常默认为object,如果它看起来是一个从结构中受益的东西,作为一个规则,它是一个相当简单的对象,只包含作为结构实现的简单类型,那么我会仔细考虑并确定它是否有意义。

    举个地址为例。作为一个班级,我们来检查一下。

    public class Address
    {
        public string AddressLine1 { get; set; }
        public string AddressLine2 { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string PostalCode { get; set; }
    }
    

    把这个对象看作一个结构。考虑到这个地址“struct”中包含的类型,如果您用这种方式编码的话。你有没有看到任何不符合你要求的东西?考虑潜在的性能优势(例如,是否有一个)?

        7
  •  1
  •   Vladimir    11 年前

    实际上,我不建议在值对象实现中使用.NET结构。有两个原因:

    • 结构不支持继承
    • 窗体不能很好地处理到结构的映射

    在这里,我将详细描述这个主题: Value Objects explained

        8
  •  0
  •   Richard    17 年前

    如果按值传递,复制实例的成本是多少?

    如果高,则为不可变引用(类)类型,否则为值(结构)类型。

        9
  •  0
  •   Michael Damatov    17 年前

    根据经验法则,结构大小不应超过16个字节,否则在方法之间传递它可能比传递对象引用(在32位计算机上)长4个字节的对象引用更昂贵。

    另一个问题是默认构造函数。结构体 总是 具有默认(无参数和公共)构造函数,否则

    T[] array = new T[10]; // array with 10 values
    

    不起作用。

    此外,结构重写 == 以及 != 操作员和执行 IEquatable<T> 接口。

        10
  •  0
  •   Av Pinzur FryGuy    17 年前

    从对象建模的角度来看,我欣赏结构,因为它们允许我使用编译器将某些参数和字段声明为不可为空。当然,没有特殊的构造函数语义(比如 Spec# ,这仅适用于具有自然“零”值的类型。(因此布莱恩·瓦特的“通过实验”答案。)

        11
  •  -2
  •   ben    16 年前

    结构严格适用于高级用户(以及out和ref)。

    是的,结构在使用ref时可以提供很好的性能,但是您必须查看它们使用的内存。谁控制记忆等。

    如果您不在结构中使用ref和out,那么它们就不值得使用,如果您希望使用一些讨厌的bug:-)