C#属性和它们的不同用法
我学习C#的部分来自于不同的资源,因为每个人都有不同的学习起点。一本书重点依赖于可以使生活更简单的Visual Studio,会使人忽视建立.NET项目的本质,一旦可以从scratch中得到一切,那确实是挠痒痒,进步缓慢。
当我学会如何创建类的时候,我学到的一种结构就是属性。属性有一点令人困惑的语法,如下面描述的那样:
1 public class MyRiches 2 { 3 // Normal Data Members 4 public int Money; 5 // Properties 6 public int GoldInKgs { get; set; } 7 }
当我学习属性的时候,我一直在思考为什么我要花额外的精力去定义一种数据成员,当我可以用一种更简单和更为熟悉的方式来创建时。我认为它是语法糖,也仅仅是用于可见的糖尿病。然后在我的代码库中,我看到了属性的一种有趣的用法。
1 public class MyClass 2 { 3 public IList<SomeType> Property 4 { 5 get 6 { 7 if(m_property == null) 8 { 9 m_property = new List<SomeType>(); 10 } 11 return m_property; 12 } 13 set 14 { 15 m_property = value; 16 } 17 } 18 private IList<SomeType> m_property; 19 }
我突然担心我可疑的C#知识。因此,我开启了一段旅程,去探索真实的用处和各种模式的用法(不用担心,我稍后会解释这个示例)。
属性到底是什么呢?
在讨论属性为什么存在之前,我们先假设属性不存在。现在类中有一个私有的数据成员,但是我需要在其它类中访问它的值,却不允许修改它的值,示例如下:
1 public class Bank 2 { 3 private int AccountBalance; 4 } 5 6 public class Me 7 { 8 var myBankAccount = new Bank(); 9 var myMoney = myBankAccount.AccountBalance; 10 }
现在我不能直接访问AccountBalance
属性。我将它设置为public,尽管对于我来说比较有趣,但是可能银行不这么认为,因为我会这么做:
1 // The easy way of becoming a millionaire 2 myBankAccount.AccountBalance = 1000000;
所以说解决方案是什么呢?对的,getters(和setters,同胞兄弟)。我们可以很容易地定义一个getter数据成员,可以提供数值但是不会让我们修改它,像下面这样:
1 public class Bank 2 { 3 private int AccountBalance; 4 public int getAccountBalance() 5 { 6 return AccountBalance; 7 } 8 } 9 10 public class Me 11 { 12 var myBankAccount = new Bank(); 13 var myMoney = myBankAccount.getAccountBalance(); 14 }
如果你习惯用Eclipse写Java,你知道它有一个功能可以自动创建getters和setters,我相信C#的创建者在Visual Studio中已经加载了此功能,但是对于没有用过的人来说,写getters和setters是个冗长的函数。
这就是为什么,C#开发人员创建了属性:
属性提供了一种可以访问私有数据成员的机制
如果你不是一个喜欢繁琐的人,那基本上意味着,写自定义的getter和setter函数是一种简便的快捷方式。或者,用另一种方式来说,它们其实是一种语法糖,不想被C#属性的语法规则来打扰的话,可以参考这个链接:https://www.geeksforgeeks.org/c-sharp-properties/
现在有趣的部分来了,属性的各种使用模式。get和set不仅仅是作作样子,属性可以自定义并编译运行各种有趣且有用的代码模式。
C#属性的延迟加载
属性可以利用延迟加载功能运行缓存:
1 private int m_IncomeTax; 2 3 public int IncomeTax 4 { 5 get 6 { 7 if(m_IncomeTax == null) 8 { 9 m_IncomeTax = AReallyLongComputationForTax(); 10 } 11 return m_IncomeTax; 12 } 13 }
这是属性的基本用法,当然不是限制性访问。
Future Proofing Code
你想要维护类的API,但是逻辑或运算发生改变。在不影响你创建的类的API的情况下,你可以更改setter代码。
依然以上面的范例来说,以计算税费为例,要加入cess税,可以像下面示例一样更改getter代码,这样的话IncomeTax属性就会给出总的税费。
1 // Old 2 public int IncomeTax 3 { 4 get 5 { 6 return m_IncomeTax; 7 } 8 } 9 10 // New 11 public int IncomeTax 12 { 13 get 14 { 15 return m_IncomeTax + Cess; 16 } 17 }
创建一个协议
属性可以为类创建一个协议或API。访问类成员的正确方式可能是私有的或是私有成员的运算。如果你需要对私有成员进行额外计算,这些方式是很有用的。
一般来说,指针是用来将运行(字段)和API(属性)分离开的。稍后会看到,在不破坏源代码或二进制兼容性的情况下,将逻辑、日志等放在属性中,但是更重要地,你的数据类型想要做什么,而不是它如何去做。
用属性返回Null
开头承诺的解释代码,对于引用类型,如下:
1 public class MyClass 2 { 3 public IList<SomeType> Property 4 { 5 get 6 { 7 if(m_property == null) 8 { 9 m_property = new List<SomeType>(); 10 } 11 return m_property; 12 } 13 set 14 { 15 m_property = value; 16 } 17 } 18 19 private IList<SomeType> m_property; 20 }
这段代码实质上是检验给定的成员是否为NULL,如果为null,首先会进行赋值,然后返回数值。这种方式有很大的好处。假设属性值没有null检查,此后你会确保不会设置一个null值并减少代码数量,检查已写的内容,我们都希望写更少的代码。
最后,我知道属性像是美化了的setters/getters,上面提到的所有好处用setters/getters也能做到,也就是说,他们就仅仅是种语法糖。学会使用他们进行访问控制,而不是使用公共的数据成员,可以使代码更加健康。