【问题标题】:C#, immutability and public readonly fieldsC#、不变性和公共只读字段
【发布时间】:2010-02-12 06:10:18
【问题描述】:

我在很多地方读到公开公开字段不是一个好主意,因为如果你以后想要更改属性,你将不得不重新编译所有使用你的类的代码。

但是,在不可变类的情况下,我不明白您为什么需要更改属性 - 毕竟您不会向“集合”添加逻辑。

对此有什么想法,我错过了什么吗?

差异示例,对于那些阅读代码比阅读文本更容易的人:)

//Immutable Tuple using public readonly fields
public class Tuple<T1,T2>
{
     public readonly T1 Item1;
     public readonly T2 Item2;
     public Tuple(T1 item1, T2 item2)
     {
         Item1 = item1;
         Item2 = item2;
     }
}

//Immutable Tuple using public properties and private readonly fields
public class Tuple<T1,T2>
{
     private readonly T1 _Item1;
     private readonly T2 _Item2;
     public Tuple(T1 item1, T2 item2)
     {
         _Item1 = item1;
         _Item2 = item2;
     }
     public T1 Item1 { get { return _Item1; } }
     public T2 Item2 { get { return _Item2; } } 
}

当然,您可以使用自动属性 ​​(public T1 Item1 { get; private set; }),但这只会让您“同意不变性”而不是“保证不变性”...

【问题讨论】:

    标签: c# properties field immutability


    【解决方案1】:

    C# 6.0 现在支持自动属性初始化器。

    自动属性初始化器允许直接分配属性 在他们的声明中。对于只读属性,它负责 确保财产不可变所需的所有仪式。

    您可以在构造函数中初始化只读属性或使用自动初始化器

    public class Customer
    {
        public Customer3(string firstName, string lastName)
        {
            FirstName = firstName;
            LastName = lastName;
        }
        public string FirstName { get; }
        public string LastName { get; }
        public string Company { get; } = "Microsoft";
    }
    
    var customer = new Customer("Bill", "Gates");
    

    你可以阅读更多关于自动属性初始化器here

    【讨论】:

      【解决方案2】:

      这是一个明显的属性遗漏,你不能写这样的东西:

      public T2 Item2 { get; readonly set; } 
      

      我什至不确定readonly 是用来表示“只能在构造函数中设置”的最佳词,但这就是我们所坚持的。

      这实际上是很多人要求的功能,所以我们希望它很快会在假设的 C# 新版本中引入。

      this related question

      【讨论】:

      • 嗯,一个“假设的 C# 新版本”我想我知道你一直在阅读谁的博客 ;)
      • 是的,我们正在考虑。假设。这实际上是一个比乍看起来要困难得多的问题。
      • @Eric Lippert,现在你只是在取笑我们。我希望很快就会有一篇关于困难的帖子:)
      • @Benjol:棘手的一点是,非常希望使此类属性的初始化看起来像匿名类型的初始化,或者像对象初始化器一样。但是在前者中,我们生成了构造函数,而在后者中,属性在构造后发生了变化。如何合理化所有不明显的事情。
      • @Eric,没有办法将 Naj 安装在某个地方吗? :)
      【解决方案3】:

      您以后可能不需要向 setter 添加任何逻辑,但您可能需要向 getter 添加逻辑。

      这是我使用属性而不是公开字段的充分理由。

      如果我感觉严谨,那么我会选择完全不变性(显式 readonly 支持字段,带有暴露的 getter 而没有 setter)。如果我觉得懒惰,那么我可能会选择“同意不变性”(具有暴露的 getter 和私有 setter 的自动属性)。

      【讨论】:

        【解决方案4】:

        在 C#9 中,我们有 init 访问器,您可以使用它来代替 set 访问器。

        var firstCar = new Car { Color = "Orange", Brand = "Mclaren" };
                
        public class Car
        {
            public string Color { get; init; }
                        
            public string Brand { get; init; }
        }
        

        使用 init 声明仅 init 属性(或索引器) 访问器代替集合访问器

        考虑包含 init 访问器的实例属性 在以下情况下可设置,除非在本地 函数或 lambda:

        • 在对象初始化期间
        • 在 with 表达式初始化器期间
        • 在包含或派生类型的实例构造函数中,在 这个或基础
        • 在任何属性的 init 访问器中,在 this 或 base 上
        • 带有命名参数的内部属性用法

        Init only setters

        【讨论】:

          【解决方案5】:

          作为标准做法,我遵循您的第二个示例,仅在对象是公共的或易受攻击的无意篡改时使用“只读”。我在当前构建插件框架的项目中使用“商定不变性”模型。显然,由于商定的不变性,readonly 保护被移除。

          只有在极少数情况下,我才会公开一个字段 - 公共、内部或其他。除非写一个属性 {get;} 花费的时间超过我愿意付出的时间,否则感觉不对。

          【讨论】:

            【解决方案6】:

            属性背后的想法是,即使您现在或以后不打算更改它们,也可能需要以某种不可预见的方式进行更改。假设您需要更改 getter 以进行某种计算或记录。也许您需要添加异常处理。很多潜在的原因。

            还要考虑语义。如果 T1 是值类型而不是引用类型,则访问 obj.Item1 会在 getter 中返回 _Item1 的副本,而在没有 getter 的情况下访问 Item1 将不会检索到副本。这意味着虽然 Item1 在内部可能是不可变的,但返回的值类型对象却不是。我想不出为什么这会是一件的事情,但这是不同的。

            【讨论】:

            • 通过属性返回时复制是什么意思?你有这方面的参考吗?
            • 值类型在传递时必须被复制。这就是 C# 的工作原理。
            • 是的,但是为什么在传递时也不会复制该字段?我看不出这会是什么问题。
            • 如果你通过了。 obj.field.ToString() 适用于原始实例。 obj.property.ToString() 如果它是一个值类型,则适用于副本。
            猜你喜欢
            • 1970-01-01
            • 2017-02-23
            • 2014-09-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-03-24
            • 1970-01-01
            相关资源
            最近更新 更多