C#,不变性和公共只读字段

时间:2010-02-12 06:10:18

标签: c# properties field immutability

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

但是,在不可变类的情况下,我不明白为什么你需要更改为属性 - 毕竟你不会在'set'中添加逻辑。

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

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

//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; }),但这只会让您“同意不变”,而不是“保证不变性”......

6 个答案:

答案 0 :(得分:12)

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

的更多信息

答案 1 :(得分:11)

属性明显遗漏,你不能写出像:

public T2 Item2 { get; readonly set; } 

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

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

请参阅this related question

答案 2 :(得分:4)

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

这足以让我使用属性而不是暴露字段。

如果我感觉严格,那么我会寻求完全不变的(明确的readonly支持字段与暴露的getter和没有setter)。如果我感到懒惰,那么我可能会选择“同意的不变性”(与暴露的吸气剂和私人制定者的自动属性)。

答案 3 :(得分:1)

在 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 only 属性(或索引器)是通过使用 init 声明的 访问器代替集合访问器

<块引用>

一个包含 init 访问器的实例属性被认为是 在以下情况下可设置,除非在本地 函数或 lambda:

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

Init only setters

答案 4 :(得分:0)

作为一种标准做法,我只关注你的第二个例子,当对象是公共的或易受攻击时,只能使用'readonly'来进行无意中的篡改。我在构建插件框架的当前项目中使用“商定的不变性”模型。显然,在商定的不变性的情况下,readonly保护被删除。

仅在极少数情况下才会公开字段 - 公共字段,内部字段或其他字段。只是感觉不对,除非写一个属性{get;}比我愿意花费更多的时间。

答案 5 :(得分:0)

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

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