【问题标题】:Using public readonly fields for immutable structs instead of private field/public getter pairs对不可变结构使用公共只读字段,而不是私有字段/公共 getter 对
【发布时间】:2014-09-09 13:33:02
【问题描述】:

这是我第一次编写将用于广泛的几何计算的小型不可变结构。我很想使用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;
    }
}

它看起来更干净,并消除了额外的层(吸气剂)。除了使用公共字段是不好的做法之外,以这种方式使用公共只读字段是否有任何负面影响?

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

更新:

感谢所有输入。在这种不使用数据绑定等的情况下,使用public readonly 字段似乎没有缺点。在我的基准测试中,执行时间减少了 70%,这是一个大问题。以 .NET 4 为目标,我希望编译器能够内联 getter-only 属性。基准测试当然是在没有附加调试器的情况下在发布配置中测试的。

【问题讨论】:

  • 正如@asawyer 指出的那样,这是我过去对结构所做的,尽管只读是一个有趣的想法。 readonly 背后的想法是它们只能通过声明或构造函数来设置。由于结构 supposed 是不可变的,所以对我来说这听起来是个好主意。我希望收到知道他们在说什么的人的回复,以了解这是否是有效的做法:O
  • @asawyer 请注意,不能在 struct 构造函数中使用自动属性,因为您将立即得到“在将其所有字段分配给之前无法使用 'this' 对象”。因此,如果您需要一个构造函数来为结构的属性赋值,非自动属性是可行的方法。
  • 在这种情况下,我会向 .NET Framework 寻求指导。框架中的结构是否使用字段或属性?答案是属性,所以我倾向于效仿。框架仅真正将字段用于常量。在其他条件相同的情况下,属性具有能够绑定的优势,哪些字段不能,并且还允许您在不更改接口的情况下更改未来的实现。面向未来不会受到伤害。
  • @LasseV.Karlsen 我不记得了,谢谢。我通常在 struts 上使用带有私有 ctor 的静态辅助函数。
  • 使用属性而不是只读字段通常是为了向前兼容。如果您稍后决定可以计算其中一个属性,并将其更改为进行计算的属性,则所有已经使用该结构引用您的程序集的现有程序集也需要重新编译。此外,调用仅返回私有字段值的属性 getter可能被内联到调用代码中,因此不会产生调用开销。

标签: c# .net field readonly getter-setter


【解决方案1】:

.NET Framework 设计指南say about fields

请勿提供公开或受保护的实例字段。

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

在某些情况下,良好的库设计要求您违反这些设计准则。这种情况应该很少见,重要的是您必须有明确且令人信服的理由做出决定。

那么这对您的代码意味着什么?一个关键问题是您的代码的受众。您是否正在编写指南定义的“框架”,它是“扩展 .NET Framework 并与之交互的库”之一?

如果您正在编写一个框架,那么您会遇到大多数代码所没有的约束。许多其他代码将取决于您的代码,对重大更改很敏感,并且需要与您的 DLL 的二进制兼容性。这就是为什么属性对框架很重要的原因。如果更高版本的框架需要向 getter 或 setter 添加逻辑,则从字段更改为属性会破坏二进制兼容性。

大多数代码不是框架的一部分。依赖关系很少,如果有一个简单的重大更改,可以很容易地修复。例如,依赖代码可能使用outref 参数中的字段。如果稍后将该字段更改为属性以使其具有某些逻辑,则修复损坏的代码通常没什么大不了的。编译器将引导您解决问题。同样,也不需要二进制兼容性。如果不重新编译依赖项,您将永远不会放入更新的 DLL。

对于非框架代码,如果以后将其更改为属性的机会很小,则最好使用字段。字段更灵活,属性更佳,在某些情况下更快。

【讨论】:

    【解决方案2】:

    自动属性可能会有所帮助。

    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 只保护引用——也就是说,字段必须始终引用同一个对象,但如果被写入允许被引用的对象本身可能会发生变异。我怀疑许多没有经验的程序员会被这种微妙之处绊倒。

    鉴于您衡量的性能优势,我认为您有合理的理由使用公共只读字段,这是一个判断电话。权衡是你允许你的类型的消费者与你的结构的内部紧密耦合,基本上牺牲了封装。对于这种受控条件下的简单类型并具有经过验证的性能优势,这可能是合理的。

    【讨论】:

    • 这不会编译。您不能在结构构造函数中设置自动属性。
    • 我知道自动属性并慷慨地使用它们。然而,在这种情况下,与属性相反,使用只读字段的计算基准下降到 30% 的时间。这对于 CPU 密集型代码来说是个大问题。
    • @RaheelKhan 您配置了 DEBUG 构建还是 RELEASE 构建?
    • 为什么投反对票?请留下评论来解释投票,以便改进答案。
    • @RaheelKhan +1 我同意,我希望人们能说明他们为什么赞成/反对投票,这样我们都会学到更多。需要明确的是,我认为否决票是完全合理的,因为我的代码中有一个编译错误,投票者应该把我叫出来,这样我才能纠正它。
    【解决方案3】:

    诸如“Prefer public properties over public fields”之类的准则是准则,而不是规则。

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

    他关于使用字段而不是属性的主要论点是,PointVectorVertex 这样的原语通常没有非法值,因此几乎不需要或不需要添加 getter/setter 层。

    他还为具有 可变 字段提出了很好的论据,但无论如何这不是你的情况。

    但我想自己补充一点供您考虑:您的类会被用于数据绑定吗?数据绑定仅适用于属性,不适用于字段。

    【讨论】:

    • 感谢您的链接。在我的情况下,可变性不是一个选项,因为这些对象需要大量使用字典、哈希集等进行处理。不,此代码不适用于数据绑定或任何 UI 场景。
    • @RaheelKhan 那么你的情况可能是使用公共只读字段的好人选:)
    【解决方案4】:

    在没有反射的纯 C# 中,在您的情况下几乎没有理由避免使用只读字段,我自己可能会选择只读字段。属性的大多数一般优势在这里并不适用。那就是……

    任何使用反射来获取属性列表并作用于这些属性的东西,在未经修改的情况下将无法与字段(无论是否只读)一起使用。

    特别是,将属性更改为字段可能会导致数据绑定停止工作。它将继续编译而没有任何问题,但它不再做你想做的事。如果您有任何此类代码,或者您预计将来会出现此类代码,则需要继续使用属性。

    【讨论】:

    • +1 用于提及数据绑定。并不是说它适用于我的情况,但是您如何看待字段的反射问题?还是您的意思是如果将属性更改为字段?
    • @RaheelKhan 数据绑定是使用仅查看属性的反射的代码示例。 typeof(Vector4).GetProperties() 不会包含您的只读字段;为此,应使用typeof(Vector4).GetFields()。如果(与数据绑定一样)对GetProperties() 的调用不在您自己的代码中,那么您将陷入困境。
    • +1 一个很好的答案。特别是,“……[如果]您预计将来会出现这样的代码……”——谁知道未来会怎样?仅出于这个原因,我很难证明牺牲属性的封装是合理的,除非存在相当严重的性能影响。 70% 听起来很严重,但如果只使用几次课程,那绝对是微不足道的。情况规则。
    猜你喜欢
    • 1970-01-01
    • 2017-12-09
    • 2020-12-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-10
    • 1970-01-01
    • 2013-07-26
    相关资源
    最近更新 更多