私人比私人更私密? (C#)

有时您有一个支持属性的私有字段,您只需要通过属性设置器设置字段,以便在字段更改时可以执行其他处理。 问题是,从同一类的其他方法中意外绕过属性setter仍然很容易,并且没有注意到你已经这样做了。 有没有办法在C#中解决这个或一般的设计原则来避免它?

恕我直言,它没有被使用,因为:

  • class级必须信任自己
  • 如果你的class级变得那么大,一部分不知道另一部分,那就应该划分。
  • 如果属性背后的逻辑稍微复杂一些,请考虑将其封装在自己的类型中。

我认为这是一个讨厌的黑客,并尽可能避免它,但……

您可以将支持字段标记为已过时,以便编译器在您尝试访问它时将生成警告,然后禁止该属性getter / setter的警告 。

如果属性具有自定义消息,则需要禁用的警告代码是用于普通Obsolete属性的CS0612和用于CS0618的警告代码。

 [Obsolete("Please don't touch the backing field!")] private int _backingField; public int YourProperty { #pragma warning disable 612, 618 get { return _backingField; } set { _backingField = value; } #pragma warning restore 612, 618 } 

没有内置的方法来做你想做的事情,但是通过事物的声音,你需要在你的类和那个值之间进行另一层抽象。

创建一个单独的类并将项目放在那里,然后您的外部类包含新类,并且您只能通过其属性访问它。

不,没有。 我自己也很喜欢这个 – 有点像:

 public string Name { private string name; // Only accessible within the property get { return name; /* Extra processing here */ } set { name = value; /* Extra processing here */ } } 

我想我大约5年前首次在C#新闻组中提出过这个问题……我不希望看到它发生过。

在序列化等方面有各种各样的皱纹要考虑,但我仍然觉得它会很好。 我宁愿先自动实现readonly属性,但……

您可以通过在构造函数(或其他初始化函数)中使用本地闭包来执行此操作。 但它需要帮助程序类更多的工作。

 class MyClass { private Func reallyPrivateFieldGetter; private Action reallyPrivateFieldSetter; private Foo ReallyPrivateBackingFieldProperty { get { return reallyPrivateFieldGetter(); } set { reallyPrivateFieldSetter(value); } } public MyClass() { Foo reallyPrivateField = 0; reallyPrivateFieldGetter = () => { return reallyPrivateField; } reallyPrivateFieldSetter = v => { reallyPrivateField = v; }; } } 

我怀疑底层字段类型Foo需要是一个引用类,因此两个闭包是在同一个对象上创建的。

C#中没有这样的配置。

但是我会以不同的方式命名私有变量(例如m_something或只是_something),因此在使用它时更容易发现它。

您可以将所有私有字段放入嵌套类中,并通过公共属性公开它们。 然后在您的类中,您实例化该嵌套类并使用它。 这样,如果私有字段属于主类,则无法访问它们。

 public class A { class FieldsForA { private int number; public int Number { get { //TODO: Extra logic. return number; } set { //TODO: Extra logic. number = value; } } } FieldsForA fields = new FieldsForA(); public int Number { get{ return fields.Number;} set{ fields.Number = value;} } } 

它只是提供了一定程度的阻碍。 访问私有支持字段的根本问题仍然存在于嵌套类中。 但是,A类中的代码无法访问嵌套类FieldForA的私有字段。 它必须通过公共财产。

也许是一个属性支持商店,类似于WPF存储属性的方式?

所以,你可以:

 Dictionary mPropertyBackingStore = new Dictionary (); public PropertyThing MyPropertyThing { get { return mPropertyBackingStore["MyPropertyThing"] as PropertyThing; } set { mPropertyBackingStore["MyPropertyThing"] = value; } } 

你可以做你现在想要的所有预处理,知道如果有人直接访问变量,那么与属性访问器相比真的很难。

PS您甚至可以使用WPF中的依赖属性基础结构…
PPS这显然会产生铸造成本,但这取决于您的需求 – 如果性能至关重要,也许这不是您的解决方案。
PPPS不要忘记初始化后备存储! (;

编辑:

实际上,如果您将存储的value属性更改为属性存储对象(例如,使用Command模式),您可以在命令对象中进行处理…只是一个想法。

不能在标准C#中做到这一点,但你可以

  • 定义一个自定义属性,例如OnlyAccessFromProperty

  • 写你的代码就像

     [OnlyAccessFromProperty(Name)] String name Name { get{return name;} } etc … 
  • 然后为FxCop (或其他检查程序)编写自定义规则

  • FxCop添加到构建系统中,这样如果您的自定义规则发现错误,则构建失败。

我们是否需要一组标准的自定义规则/属性来强制执行此类常见设计专利,而无需扩展C#

C#没有语言function。 但是,您可以依赖命名约定,类似于根本没有私有属性的语言。 使用_p_您的私有变量名称添加_p_ ,您将非常确定不会意外键入它。

我不知道C#但是在Java中你可能有一个只有私有实例变量和公共setter和getter的基类(应该返回实例var的副本)并在inheritance的类中执行所有其他操作。

“一般设计原则”是“使用inheritance”。

在C#中没有构建解决方案,但我认为你的问题可以通过良好的OO设计来解决:每个类应该只有一个目的。 因此,尝试将字段周围的逻辑提取到尽可能小的类中。 这减少了您可以意外访问该字段的代码。 如果你偶然犯了这样的错误,你的class级可能很大。

通常,接口可以限制仅访问对象的某个“子集”。 如果适合您的情况取决于您当然的设置。 有关要完成的工作的更多细节将有助于提供更好的答案。

你说你做了额外的处理。 据推测,这可以在正确的条件下检测到。 那么,我的解决方案是创建实现条件的unit testing,以便如果直接使用支持字段,则测试将失败。 使用这些测试,只要测试通过,您就应该能够确保代码正确使用属性接口。

这样做的好处是您不需要牺牲您的设计。 您可以获得unit testing的安全性,以确保您不会意外地进行重大更改,并且您可以了解课程的工作原理,以便稍后出现的其他人可以将您的测试作为“文档”阅读。

把它包在课堂上? 无论如何,财产的事情有点像,将数据与方法联系起来 – 他们曾经狂热的“封装”……

 class MyInt { private int n; public static implicit operator MyInt(int v) // Set { MyInt tmp = new MyInt(); tmp.n = v; return tmp; } public static implicit operator int(MyInt v) // Get { return vn; } } class MyClass { private MyInt myint; public void func() { myint = 5; myint.n = 2; // Can't do this. myint = myint + 5 * 4; // Works just like an int. } } 

我确定我错过了什么? 这似乎太正常了……

顺便说一下,我确实喜欢关闭一个,非常疯狂。

我最喜欢的解决方案(以及我所遵循的)是命名永远不打算直接使用前导下划线的私有支持字段,以及打算在没有下划线(但仍然是小写)的情况下使用的私有字段。

我讨厌键入下划线,所以如果我开始访问以下划线开头的变量,我知道有些错误 – 我不应该直接访问该变量。 显然,这种方法仍然不会最终阻止您访问该领域,但正如您从其他答案中可以看到的那样,任何方法都可以解决这个问题,并且/或者几乎不可行。

使用下划线表示法的另一个好处是,当您使用下拉框浏览您的类时,它会将所有私有的,永远不会使用的后备字段全部放在列表顶部的一个位置,而不是允许他们与各自的财产混在一起。

作为一种设计实践,您可以使用与普通公共成员不同的“私有属性”的命名约定 – 例如,对于私有项使用m_ItemName而对于公共项使用m_ItemName

如果您使用的是C#3.0编译器,则可以定义具有编译器生成的支持字段的属性,如下所示:

 public int MyInt { get; set; } 

这意味着只有一种方法可以访问该属性,当然这并不意味着您只能访问该字段,但它确实意味着除了要访问的属性之外什么都没有。

我同意这个类应该信任自己的一般规则(以及推断任何在课堂上编码的人)。 通过智能感知暴露该场是一种耻辱。
遗憾的是,放置[EditorBrowsable(EditorBrowsableState.Never)]在该类(或者实际上是程序集(1))中不起作用

在Visual C#中,EditorBrowsableAttribute不会抑制同一程序集中的类的成员。

如果你真的希望解决它的这个方面,下面的类可能是有用的,并使意图也清晰。

 public sealed class TriggerField { private T data; ///raised *after* the value changes, (old, new) public event Action OnSet; public TriggerField() { } ///the initial value does NOT trigger the onSet public TriggerField(T initial) { this.data=initial; } public TriggerField(Action onSet) { this.OnSet += onSet; } ///the initial value does NOT trigger the onSet public TriggerField(Action onSet, T initial) : this(onSet) { this.data=initial; } public T Value { get { return this.data;} set { var old = this.data; this.data = value; if (this.OnSet != null) this.OnSet(old, value); } } } 

允许你(有点冗长地)使用它:

 public class Foo { private readonly TriggerField flibble = new TriggerField(); private int versionCount = 0; public Foo() { flibble.OnSet += (old,current) => this.versionCount++; } public string Flibble { get { return this.flibble.Value; } set { this.flibble.Value = value; } } } 

或者你可以选择一个不那么详细的选项,但访问Flibble是不是惯用的bar.Flibble.Value = "x"; 这在反思场景中会有问题

 public class Bar { public readonly TriggerField Flibble; private int versionCount = 0; public Bar() { Flibble = new TriggerField((old,current) => this.versionCount++); } } 

  1. 或者如果你看一下社区内容的解决方案!

.net 4.0中新的Lazy类

为几种常见的延迟初始化模式提供支持

根据我的经验,这是我希望将一个字段正确包装在私有中的最常见原因,因此很好地解决了一个常见的情况。 (如果您还没有使用.Net 4,那么您可以使用与.Net 4版本相同的API创建自己的“Lazy”类。)

有关使用Lazy类的详细信息,请参阅this和this以及此内容。

使用“veryprivate”构造类型

例:

 veryprivate void YourMethod() { // code here }