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

时间:2009-08-19 11:10:31

标签: c# properties field

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

20 个答案:

答案 0 :(得分:41)

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

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

答案 1 :(得分:22)

我认为这是一个讨厌的黑客,如果可能的话尽量避免它,但是......

您可以将支持字段标记为过时,以便编译器在您尝试访问时生成警告,然后suppress that warning获取属性getter / setter。

如果属性包含自定义消息,则您需要取消的警告代码为CS0612 Obsolete属性和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
}

答案 2 :(得分:20)

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

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

答案 3 :(得分:11)

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

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属性,但是......

答案 4 :(得分:7)

CAN 通过在构造函数(或其他初始化函数)中使用本地闭包来执行此操作。但helper class方法需要更多的工作。

class MyClass {
  private Func<Foo> reallyPrivateFieldGetter;
  private Action<Foo> reallyPrivateFieldSetter;
  private Foo ReallyPrivateBackingFieldProperty {
    get { return reallyPrivateFieldGetter(); }
    set { reallyPrivateFieldSetter(value); }
  }

  public MyClass() {
    Foo reallyPrivateField = 0;
    reallyPrivateFieldGetter = () => { return reallyPrivateField; }
    reallyPrivateFieldSetter = v => { reallyPrivateField = v; };
  }
}

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

答案 5 :(得分:5)

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

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的私有字段。它必须经过公共场所。

答案 6 :(得分:5)

C#中没有这样的配置。

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

答案 7 :(得分:3)

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

所以,你可以:

Dictionary<string,object> mPropertyBackingStore = new Dictionary<string,object> ();

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

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

P.S。您甚至可以使用WPF中的依赖属性基础结构...
P.P.S.这显然会导致铸造成本,但这取决于您的需求 - 如果性能至关重要,也许这不是您的解决方案。
P.P.P.S不要忘记初始化后备商店! (;

修改

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

答案 8 :(得分:3)

无法在标准C#中执行此操作,但您可以

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

  • 编写代码,如

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

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

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

答案 9 :(得分:2)

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

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

答案 10 :(得分:2)

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

答案 11 :(得分:1)

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

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

答案 12 :(得分:1)

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

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

答案 13 :(得分:1)

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

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 v.n;
    }
}

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.
    }
}

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

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

答案 14 :(得分:1)

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

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

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

答案 15 :(得分:0)

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

public int MyInt { get; set; }

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

答案 16 :(得分:0)

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

答案 17 :(得分:0)

我同意一般规则,即班级应该信任自己(并推断任何在班级内编码的人)。 遗憾的是,该场是通过智能感知暴露出来的 遗憾的是,放置[EditorBrowsable(EditorBrowsableState.Never)]并不适用于该类(或者实际上是程序集(1))

  

在Visual C#中,EditorBrowsableAttribute不会禁止来自同一程序集中的类的成员。

如果您确实希望解决这方面的问题,那么以下类可能会有用,并且 intent 也可以清除。

public sealed class TriggerField<T>
{
    private T data;

    ///<summary>raised *after* the value changes, (old, new)</summary>
    public event Action<T,T> OnSet;

    public TriggerField() { }

    ///<summary>the initial value does NOT trigger the onSet</summary>
    public TriggerField(T initial) { this.data=initial; }

    public TriggerField(Action<T,T> onSet) { this.OnSet += onSet; }

    ///<summary>the initial value does NOT trigger the onSet</summary>
    public TriggerField(Action<T,T> 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<string> flibble = new TriggerField<string>();
    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<string> Flibble;
    private int versionCount = 0;

    public Bar()
    {
        Flibble = new TriggerField<string>((old,current) => this.versionCount++);
    }
}

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

答案 18 :(得分:0)

.net 4.0中的新Lazy类

  

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

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

有关使用Lazy类的详细信息,请参阅thisthis以及this

答案 19 :(得分:-1)

使用“veryprivate”构造类型

示例:

veryprivate void YourMethod()
{

  // code here
}