了解私人二传手


90

我不了解需要以C#2开头的私有设置器。

对我来说,使用setter方法可以让用户在该类中设置一些变量。这样做时,我们不会将变量直接暴露给用户。相反,我们让他们通过此公共设置器方法来执行此操作。

对我来说这是使用“封装”。有一些论点声称私有设置程序将允许您应用封装。

我不是通过公共setter方法使用封装吗?为什么我们需要私人二传手?

不可变的类和带有私有二传手的类有什么区别?


1
我非常喜欢私人安装员的时间-帮助我重新安排了丑陋的课堂。它们也使得不可能像这样这样立即声明并设置一个非常量实例变量:private File settingsFile = null;然后在一个构造函数中:if (settingsFile == null) { settingsFile = GetSettingsFile() };。像这样的重构代码有时让我哭了:)。仅仅因为您可以在构造函数之前设置一个成员,并不意味着您应该(对于多个构造函数)使它很难遵循逻辑。专用设置器会强制您在构造函数或更高版本内部设置值。
Hamish Grubijan

Answers:


261

逻辑上。

私有设置器的存在是因为您可以使用auto属性:

public int MyProperty { get; set; }

如果要使其只读,该怎么办?

public int MyProperty { get; }

哦,废话!我无法从自己的班级访问它;我应该像普通属性一样创建它:

private int myProperty;
public int MyProperty { get { return myProperty; } }

嗯...但是我失去了“自动属性”功能...

public int MyProperty { get; private set; }

AHHH ..那更好!!


谢谢。这再次有意义
Dene

3
@ktutnik感谢您按照您的方式进行布局。现在,这对我也很有意义!
Vivek M. Chawla

3
出色地说明了答案。
imnk 2015年

3
Oh crap!! I can't access it from my own class从C#6.0开始,这仅在初始化阶段之外才是正确的。请参阅我的答案stackoverflow.com/a/34223746/198797
tsemer

1
使用c#6添加到#tsemer的答案{get; }中不等于{ get; private set; }。对于第一种方式,property.GetSetMethod(true)返回null,而第二种true。这让我感到惊讶。
涂油画

37

如果您拥有只读属性,并且不想显式声明backing变量,则私有setter很有用。

所以:

public int MyProperty
{
    get; private set;
}

是相同的:

private int myProperty;
public int MyProperty
{
    get { return myProperty; }
}

对于非自动实现的属性,它为您提供了一种类中设置属性的一致方法,这样,如果您需要验证等,则只能将其放在一个位置。

为了回答您的最后一个问题,MSDN在专用设置器上有以下说法:

但是,对于仅封装一组值(数据)并且几乎没有行为或几乎没有行为的小型类或结构,建议通过将集合访问器声明为私有来使对象不可变。

“自动实施的属性”的MSDN页面上


1
很抱歉,由于有私人二传手,我仍然看不到增加的价值。如果我们不想暴露二传手,那我们就只有吸气剂。如果我们要添加验证器,我们可以有一个公共设置器,并向其添加验证。为什么我们需要无法使用的二传手?我的理解方式就像“坐这辆车,但你不能开车”,如果我还是不开车,为什么要给我车
Dene

@Dene-您当然可以这样做,因为它没有错。具有自动实现的属性不是强制性的。
克里斯·

我知道我的表达方式没有错。我只是欣赏C#2所做的改进。围绕它的炒作似乎很多,但我感觉不到或看不到它的价值。
Dene

@Dene,我想念所有关于它的宣传。但是,当我最终看到可以做到这一点时,我感到很高兴,因为我知道如何清除.Net 1.1时代的一些冗长的类。尽管您可能可以使用尚未设置的属性的值,但是这样做比使用已设置为null的实例成员变量的值不自然。
Hamish Grubijan

它简化了代码。就像自动属性一样。C#的许多后续更新都是关于使代码更简洁,更易读。如果愿意,您仍然可以用其他方式进行操作-向后兼容似乎也是一个很大的目标。我想这是一个品味问题。
niico

18

这很简单。私有设置器允许您创建只读的公共或受保护属性。

而已。那是唯一的原因。

是的,您可以仅通过指定getter来创建只读属性,但是对于自动嵌入的属性,则需要同时指定get和set,因此,如果您希望将自动实现的属性设为只读,则必须使用私人二传手。没有其他方法可以做到。

的确,私有设置程序并不是专门为自动实现的只读属性创建的,但由于其他原因,它们的使用却有些深奥,主要集中在只读属性以及反射和序列化的使用上。


2
谢谢“如果您希望自动实现的属性为只读,则必须使用私有设置器”。这对我来说很有意义
Dene

17

引入了C#6.0自动属性初始化程序的语法器仅在初始化时设置的属性(内联或在构造函数中)不再需要专用设置器。

这些新语法现在可以编译:

内联初始化属性

public class MyClass1 {
  public string MyProperty { get; } = "Aloha!"
}

构造函数的初始化属性

public class MyClass2 {
  public string MyProperty { get; }

  public MyClass2(string myProperty) {
    MyProperty = myProperty;
  }
}

3
@Ziggler,的确不是。OP也不问。他只是不了解拥有它们的需要。答案:“在这种情况下,您不再需要它们”。
tsemer

6

我不了解需要以C#2开头的私有设置器。

例如,发票类允许用户从Items属性中添加或删除项目,但不允许用户更改Items引用(即,用户无法将Items属性分配给另一个项目列表对象实例)。


public class Item
{
  public string item_code;
  public int qty;

  public Item(string i, int q)
  {
    this.item_code = i;
    this.qty = q;
  }
}

public class Invoice
{
  public List Items { get; private set; }

  public Invoice()
  {
    this.Items = new List();
  }
}

public class TestInvoice
{
  public void Test()
  {
    Invoice inv = new Invoice();
    inv.Items.Add(new Item("apple", 10));

    List my_items = new List();
    my_items.Add(new Item("apple", 10));

    inv.Items = my_items;   // compilation error here.
  }
}

+1以突出显示属性可以通过公共获取器来操作,即使它们具有私有设置器也是如此。
user1725145

4

举例来说,您没有通过属性存储实际变量,也没有使用该值来计算某些内容。

在这种情况下,您可以创建一种方法进行计算

private void Calculate(int value)
{
 //...
}

或者您可以使用

public int MyProperty {get; private set;}

在这些情况下,我建议使用后者,因为属性会完整地重构每个成员元素。

除此之外,甚至说您将属性与变量映射。在这种情况下,您需要在代码中这样编写:

public int myprop;
public int MyProperty {get { return myprop;}}

... ...

this.myprop = 30;

... ...
if(this.MyProperty > 5)
   this.myprop = 40;

上面的代码看起来很恐怖,因为程序员始终需要谨慎使用MyProperty for Get和Myprop for Set。

为了保持一致性,您可以使用私有设置程序,该属性将Propoerty设为只读,而您可以在代码的内部使用它。


3

我认为有一些人对此进行了跳舞,但是对我而言,私有设置方法的价值在于即使在类中,您也可以封装属性的行为。正如abhishek所指出的,如果您想在每次属性更改时都触发一个属性更改事件,但是又不想让公众读取/写入属性,那么您要么必须使用私有设置器,要么必须引发事件,只要您修改后备字段即可。后者容易出错,因为您可能会忘记。相关地,如果更新属性值导致执行了某些计算或修改了另一个字段,或者对某些内容进行了延迟初始化,那么您还需要将其包装在私有设置器中,而不必记住在进行任何操作时都要做使用背景字段。


2

封装意味着对象的状态仅通过定义的接口发生,因此,类可以确保此状态始终有效并且与类的目的保持一致。

因此,在某些情况下,完全公开公开字段是完全符合封装原理的-该字段的所有可能值对所有其他字段的所有其他可能值均有效,因此程序员可以主动决定允许该字段由外部代码自由操纵。

尽管这些情况大多数情况下仅限于大多数都是“普通旧数据”的类。在这方面,它们也不是很有趣,所以关于它们的足够多了。

在其他情况下,在其他语言中,将具有getter和setter方法,类似于int getId()获取值并对其void setId(int val)进行更新。

通过属性,我们可以通过与用于读取和写入字段的方法相同的语法来进行读写。这是一种很好的语法糖,尽管不是必需的。

(实际上,由于反射的工作方式和情况,例如DataBinder.Eval即使某个字段可以正常工作,拥有属性也很方便,但这是另一回事)。

在引入私有设置程序之前(实际上,用C#2进行的更改是在同一块中具有私有设置程序和公共或受保护的getter的语法),我们可以有一个私有方法来完成私有设置程序的工作,因此私人二传手不是必需的。虽然它们很方便,所以尽管只是语法糖,它们还是很有用的。

封装与您的设置者是公开的,私有的,受保护的或内部的设置者无关,而与它们的适当性有关。首先默认每个字段都是私有的(就此而言readonly),然后根据需要添加更改这些字段的成员(属性或方法),并确保对象在更改时保持有效。这确保了类的不变性得到保留,这意味着描述其可以处于的有效状态集的规则永远不会被破坏(构造函数也可以通过确保其以这种有效状态开始而提供帮助)。

至于最后一个问题,要保持不变就意味着一个类没有公共的,受保护的或内部的setter ,也没有任何更改任何字段的公共的,受保护的或内部的方法。这有一定程度,在C#中有3个可能程度:

  1. 所有类的实例字段都是readonly,因此即使私有代码也无法更改它。保证它是不可变的(任何试图更改它的东西都不会编译),并且可以在此基础上进行优化。

  2. 一个类从外部是不可变的,因为没有公共成员可以进行任何更改,但是不能通过使用保证readonly从内部不进行任何更改。

  3. 从外部看,类是不可变的,尽管某些状态作为实现细节已更改。例如,可以记住一个字段,因此从外部尝试获取它只是检索相同的值,而第一个这样的尝试实际上是计算出该值,然后将其存储以供后续尝试使用。


1

如果您想支持以下方案,则需要一个私有的setter(不仅为此,而且应该指出一个很好的理由):您的类中有一个只读属性,即,仅允许类本身进行更改它,但是在构造实例后可能会更改它。对于绑定,您将需要触发一个PropertyChanged事件,最好在(私有)属性设置器中完成此操作。实际上,您可以从类中的其他任何地方触发PropertyChanged事件,但是为此使用私有设置器是“良好的公民身份”,因为您不会在整个类中分配属性更改触发器,而是将其保留在类中。财产,它所属的地方。


1

是的,您通过使用属性来使用封装,但是封装的细微之处比仅仅控制属性的读取和写入方式要多。拒绝从类外部设置属性对于鲁棒性和性能都很有用。

不变类是一经创建就不会更改的类,因此需要私有的setter(或根本不需要setter)来保护属性。

在C#3中引入的属性简写形式更频繁地使用私有设置器。在C#2中,通常只省略设置器,设置时直接访问私有数据。

这个性质:

public int Size { get; private set; }

是相同的:

private int _size;
public int Size {
  get { return _size; }
  private set { _size = value; }
}

除了,后备变量的名称是由编译器内部创建的,因此您不能直接访问它。

使用shorthand属性,需要使用私有setter创建只读属性,因为您不能直接访问backing变量。


如果我没记错的话,您在C#2.0之前getset之前都不能使用不同的访问修饰符。另外,我认为您正在混合使用2.0和3.0,因为您所指的自动实现的缩写是3.0。
Anthony Pegram

1

我不了解需要以C#2开头的私有设置器。

用例示例:

我有一个'UserInfo'包含属性的应用程序对象的实例SessionTokenIDV1不希望向类的使用者公开的属性。

我还需要能够在课堂上设置该值。

我的解决方案是封装如图所示的属性,并将setter设为私有,以便我可以设置会话令牌的值,而无需实例化代码也可以对其进行设置(甚至在我的情况下也可以看到)

public class UserInfo
{
   public String SessionTokenIDV1 { get; set; }

}


public class Example
{
  // Private vars
  private UserInfo _userInfo = new UserInfo();

  public string SessionValidV1
  {
    get { return ((_userInfo.SessionTokenIDV1 != null) && (_userInfo.SessionTokenIDV1.Length > 0)) ? "set" : "unset"; }
    private set { _userInfo.SessionTokenIDV1 = value; }
  }
}

编辑:固定代码标签编辑:示例包含已更正的错误


-1

归功于https://www.dotnetperls.com/property

专用设置器与只读字段相同。它们只能在构造函数中设置。如果尝试从外部进行设置,则会出现编译时错误。

public class MyClass
{
    public MyClass()
    {
        // Set the private property.
        this.Name = "Sample Name from Inside";
    }
     public MyClass(string name)
    {
        // Set the private property.
        this.Name = name;
    }
    string _name;
    public string Name
    {
        get
        {
            return this._name;
        }
        private set
        {
            // Can only be called in this class.
            this._name = value;
        }
    }
}

class Program
{
    static void Main()
    {
        MyClass mc = new MyClass();
        Console.WriteLine(mc.name);

        MyClass mc2 = new MyClass("Sample Name from Outside");
        Console.WriteLine(mc2.name);
    }
}

当我尝试从课堂之外进行设置时,请参见下面的屏幕截图。

在此处输入图片说明

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.