有时您有一个支持属性的私有字段,您只需要通过属性设置器设置字段,以便在字段更改时可以进行其他处理。问题是,从同一类的其他方法中意外绕过属性设置器仍然很容易,并且没有注意到你已经这样做了。在C#中是否有办法解决这个问题或一般设计原则以避免它?
答案 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++);
}
}
答案 18 :(得分:0)
.net 4.0中的新Lazy类
为几种常见提供支持 延迟初始化的模式
根据我的经验,这是我希望将一个字段正确包装在私有中的最常见原因,因此很好地解决了一个常见的情况。 (如果你没有使用.Net 4,你可以使用与.Net 4版本相同的API创建自己的“Lazy”类。)
答案 19 :(得分:-1)
使用“veryprivate”构造类型
示例:
veryprivate void YourMethod()
{
// code here
}