对不可变结构使用公共只读字段,而不是私有字段/公共getter对



这是我第一次编写将用于大量几何计算的小型不可变结构体。我想使用public readonly字段而不是private field/public getter组合。

public struct Vector4
{
    public readonly float Angle, Velocity;
    // As opposed to:
    private float _Angle, _Velocity;
    public float Angle { get { return (this._Angle); } }
    public float Velocity { get { return (this._Velocity); } }
    public Vector4 (float angle, float velocity)
    {
        // Once set in the constructor, instance values will never change.
        this.Angle = angle;
        this.Velocity = velocity;
    }
}

看起来干净多了,并且去掉了一个额外的层(getter)。除了使用公共字段是不好的做法之外,以这种方式使用公共只读字段有什么负面影响吗?

注意,我们只讨论值类型。例如,数组将暴露元素,使其被调用代码覆盖。

更新:

感谢所有的输入。对于这种不使用数据绑定等的情况,使用public readonly字段似乎没有缺点。在我的基准测试中,执行时间下降了70%,这是一个大问题。针对。net 4,我希望编译器内联只使用getter的属性。当然,基准测试是在没有附加调试器的发布配置中进行的。

在没有反射的纯c#中,避免使用只读字段的理由很少,我自己可能会选择只读字段。属性的大多数一般优点在这里并不适用。也就是说…

任何使用反射来获取属性列表并对这些属性进行操作的东西,如果不进行修改,将无法处理字段(无论是否为只读)。

特别是,将属性更改为字段可能会导致数据绑定停止工作。它将继续编译,没有任何问题,但它将不再做您希望它做的事情。如果您有这样的代码,或者您预计将来会有这样的代码,那么您需要继续使用属性。

准则,如"优先选择公共属性而不是公共字段"是准则,而不是规则。

某些情况下,使用字段的优点可能大于缺点。事实上,Rico Mariani很好地说明了一个这样的场景,在我看来,它看起来很像你的场景:

    性能测试#11:关于基于值的编程的十个问题
  • 性能测试#11:基于值的编程的十个问题:解决方案

他用字段代替属性的主要理由是,像Point, VectorVertex这样的原语通常没有非法的值,所以很少或根本不需要添加getter/setter层。

他还为可变字段做了很好的论证,但无论如何这不是你的情况。

但是我想自己加一点给你考虑:你的类会被用于数据绑定吗?数据绑定只适用于属性,不适用于字段。

.NET框架设计指南关于字段的说明:

不要提供公共或受保护的实例字段。

这是一个强有力的声明。这不是一个绝对的规则,但它非常接近指南的介绍:

在某些情况下,良好的库设计可能需要违反这些设计准则。这种情况应该是罕见的,重要的是你有一个明确的和令人信服的理由来决定你的决定。

这对你的代码意味着什么呢?关键问题是代码的受众。你正在写一个"框架"吗?正如指南所定义的那样,它是"扩展。net框架并与之交互的库"之一。

如果你正在编写一个框架,你就有了大多数代码没有的约束。许多其他代码将依赖于您的代码,对破坏性更改很敏感,并且需要与您的DLL具有二进制兼容性。这就是为什么属性对框架很重要。如果框架的后续版本需要为getter或setter添加逻辑,那么将字段更改为属性将破坏二进制兼容性。

大多数代码不是框架的一部分。依赖关系很少,如果有一个简单的破坏性更改,可以很容易地修复。例如,依赖代码可能使用outref参数中的字段。如果稍后将该字段更改为属性,以便它可以具有一些逻辑,则修复损坏的代码通常不是什么大问题。编译器会直接引导您找到问题所在。同样,也不需要二进制兼容性。在不重新编译依赖项的情况下,您将不会拖放更新后的DLL。

对于非框架代码,如果稍后将其更改为属性的可能性很小,则更可取。字段和属性更灵活,在某些情况下更快。

自动属性可能会有帮助。

public struct Vector4
    {
    public float Angle { get; private set; }
    public float Velocity { get; private set; }
    public Vector4(float angle, float velocity) : this()
        {
        // Once set in the constructor, instance values will never change.
        this.Angle = angle;
        this.Velocity = velocity;
        }
    }

[注:已更新,使其可编译-感谢Lasse V. Karlsen指出]

请注意,这与拥有一个带有支持字段的属性完全相同,除了你让编译器选择它的名称(它将是一些'不可发音'的字符串)。

属性让你烦恼的是什么?如果是冗长,那么像上面那样使用auto属性。如果您关心性能,那么您是否测量过属性与字段的影响?

有趣的是,在他的书CLR via c# 中,Jeffrey Richter对属性相当"失望",他说他希望这些属性没有被包含在CLR中。他说他更喜欢显式地使用getXXX和setXXX方法。他认为属性语法令人困惑。就我个人而言,我见过与此完全相同的Java代码,我更喜欢属性语法。

我认为值得一提的是,readonly也可能令人困惑。在这种情况下,它是明确的,因为我们一直在处理值类型,readonly"做它在罐头上说的"。然而,如果这些是引用类型,那么readonly只保护引用——也就是说,字段必须总是引用相同的对象,但是被引用的对象本身可能会被改变,如果它被写为允许这样做的话。我怀疑许多没有经验的程序员会被这种微妙的东西绊倒。

考虑到您测量的性能收益,我认为您有一个合理的理由使用公共只读字段,这是一个判断调用。这样做的代价是允许类型的使用者与结构的内部紧密耦合,本质上牺牲了封装。对于这样一个在受控条件下的简单类型,并具有已证明的性能优势,这可能是合理的。

最新更新