公共字段的最佳实践是什么?

25

当我编写一个类时,我总是通过公共属性来暴露私有字段,就像这样:

private int _MyField;
public int MyField
{ get{return _MyField; }

什么时候可以像这样公开暴露一个公共字段:

public int MyField;

我正在创建一个名为Result的结构体,我的意图是这样做:

public Result(bool result, string message)
{
   Result = result;
   Message = message;
}

public readonly int Result;
public readonly int Message;

最佳实践是什么?这样做有没有问题?


请参见此问题 - Dimitri C.
请注意,即使是.NET Framework源代码有时也会使用非私有字段。 - jrh
11个回答

32

我只有在(public)公共字段为静态常量时才暴露它们,即使是这样,我通常也会使用属性(property)。

所谓"常量"是指任何只读(readonly),不可变的值,而不仅仅是C#中可以表示为"const"的值。

即使是只读的实例变量(如Result和Message),在我的看法中也应该封装(encapsulate)在一个属性中。

更多详情请参见此文章


虽然从理论上讲你可能是对的,但过度封装会导致应用程序仍然像10年前一样缓慢,而硬件规格却增长了数倍。 - Toad
13
@Toad: 不需要...因为JIT编译器会自动内联简单的属性。 - Jon Skeet

17

9

使用属性。现在有了C#的自动属性,这变得非常容易!


2
在这里...自动属性真是太棒了! - Kon

4
最佳实践是使用属性,有几个原因。首先,它将API与底层数据结构解耦。其次,任何绑定到对象的框架都会使用属性而不是字段。
我相信还有更多的原因,但这两个对我来说已经足够了。

3

2
在C风格的语言(如C#)中,应该通过属性公开字段。这也适用于VB.NET和其他.NET语言,因为它们共享相同的底层引擎。这样做的主要原因是:
Public MyField as Integer并不等同于Public Property MyField as Integer,在.NET下的所有情况下它们的行为都不同。
然而,您可能会看到很多人继续使用公共字段。其中一些原因是由于VB6处理COM的方式。在VB6中,Public MyField as Integer等同于Public Property MyField as Integer。因为当两者都被翻译成一个COM类型库时,它们都以相同的方式实现。
这导致了VB6中对公共字段的许多奇怪限制。这些限制在.NET和许多其他面向对象的语言中不存在。这些限制在编译时被捕获,防止程序员自己给自己惹麻烦。如果您的类在VB6中是私有的,则某些限制会得到缓解,但不是全部。一般规则是只能将类和数据类型作为公共字段公开。
由于VB6的遗留问题,有很多程序员不知道.NET中存在差异。
由于.NET在这方面没有帮助我们,因此您需要采取传统的步骤,确保所有字段通过属性公开。同样,因为在.NET中,字段与属性不同。
其他人指出的文章解释了.NET方面的内容。

1
就我个人而言,这是唯一一个对“最佳实践”提出了有力论据的答案(作为一名曾经使用VB6的开发者)。我喜欢知道为什么,而不仅仅是知道什么。+1 谢谢 - Booji Boy
根据我所读的,我认为如果你仅限于类和数据类型,那么字段在行为上将等同于属性。不包括结构体和数组。 - RS Conley

2
我建议使用类似于这个的东西:
public class Result{  
    public bool Result {  get; protected set; }
    public string Message {  get; protected set; }

    public Result(bool result, string message) {
        Result = result;
        Message = message;
    }
}

这样,你就不需要声明成员变量,让编译器为你完成工作!代码非常清晰简洁,重构也非常容易。



1
通常情况下,如果一个类型将被用作数据持有者,它应该是以下两种情况之一:
一个可变的结构体,其具有公开字段,如果每个字段从类型的角度来看都像一个值,并且任意组合的字段值都“有意义”。请注意,持有对可变类类型的引用的字段可能会符合条件,如果字段的目的是保存所涉及对象的“标识”,而不是内容。结构体带有可变字段因为愚蠢的C#编译器而声名狼藉,这些编译器将`SomeThing.SomeStruct.SomeField = whatever`解释为`tempStruct = SomeThing.SomeStruct; tempStruct.SomeField = whatever`而不发出诊断,但今天的程序员没有理由担心古老的编译器会做什么。
一个不可变的类或结构体,其具有非公共字段,适用于上述情况不适用的情况,以及重新生成整个数据项以更改其中一个方面不会过于麻烦的情况。
一个可变的类,如果对它的引用不会在创建它的实体之外持久存在(这样的引用通常不应暴露给外部代码)。请注意,其他两个选项,在可行的情况下,应该被广泛优先选择,因为使用可变类类型作为数据持有者的集合(与仅存储对象的标识但不关心这些对象代表什么的集合相反)通常必须对存储在集合中的任何对象进行防御性复制,然后每次读取对象时都要进行额外的防御性复制。结构体,无论是可变的还是“不可变的”,在存储或从集合中读取时必须进行复制,但是这样的复制比使用可变类时所需的防御性复制更便宜,对于任何大小的结构体
如果没有使用可变类的实际替代方案,是否公开字段取决于类的任何版本是否需要在属性设置器中包含验证逻辑,以及成员类型是值类型还是类类型。如果BoundsRectangle类型的公共字段,则像SomeClass.Bounds.Width这样的表达式可以访问矩形的Width而无需访问其他成员。相比之下,如果Bounds是一个属性(即使是可变的),那么该表达式将不得不将Bounds的所有四个成员复制到临时结构中,然后访问该结构的Width字段。

1

我想回答一下你关于readonly的做法。

readonly不是在公共字段上仅有getter访问器的方法。我主要在私有字段上使用readonly,其中我的私有字段只能从构造函数中设置。也就是说,readonly字段只能在构造函数中设置,然后只能访问它。

最佳实践是始终使用属性来访问构造函数之后的字段。所以,如果你需要从类内部访问属性,我会这样写:

private readonly int result;
private readonly int message;

public Result(bool result, string message)
{
   this.result = result;
   this.message = message;
}

public int Result
{
   get{ return result; }
   private set { result = value; }
}

public int Message
{
   get{ return message; }
   private set { message = value; }
}

这样,您只能读取Result和Message,并仍然可以从类内部写入它。

如果使用继承,可以根据需要将设置保护。

编辑:阅读了基于问题中给出的代码后,发现Class名称Result可能会导致属性Result出错,而且我们在构造函数中接收布尔值作为结果和字符串作为消息,但尝试将它们发送到int中肯定不起作用。但是,就其价值而言,这里有一些更合理的东西:

private readonly bool result;
private readonly string message;

public Answer(bool result, string message)
{
   this.result = result;
   this.message = message;
}

public bool Result
{
   get{ return result; }
   private set { result = value; }
}

public string Message
{
   get{ return message; }
   private set { message = value; }
}

很好的解释,但是你不应该在属性上使用私有的setter,因为它们只能在运行时设置。 - Micah

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接