Santé

为明天干杯!
随笔 - 47, 文章 - 0, 评论 - 320, 阅读 - 18万
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

1 = 1 是真理还是谬误?——谈Property

Posted on   smalldust  阅读(2381)  评论(23编辑  收藏  举报

Property,这个面向时代诞生的宠儿,似乎总是被放在一个被追捧的角色。这个看似数据,实为代码的家伙,为从外部访问Class内部的私有成员提供了一个灵活的方法。

 

MS一声号召:用property吧!于是乎,原来在C++里面由于要提供外部访问而不得不声明为public的成员变量,在C#里面都安安全全地变回private field,然后再通过一个property来作为“代理”进行访问。这个代理的好处就是,可以对访问进行一定的控制。首先是可以允许只读或者只写;其次是,你可以在读写前后执行一些额外的操作,例如validation(验证赋予的值是否正确,是否在范围内等等),formatting(如按照指定的格式输出结果)等等。

 

于是,property被用得满天飞,多么简单的一个field都要用一个property来包装,哪怕只有一条a = valuereturn a

 

的确,property是有很多优点;但是凡事都是双刃剑,property,尤其是其getset的代码较为复杂的情况下,很容易引起各种意想不到的问题。究其原因,其中很重要的一个就是:很多人虽然心里知道property只不过是2method而已,仍然在使用时看作一个普通的field、变量来使用。

 

我们假设有这样一个工厂,其关于机器的一些.Net程序中,有如下一个类:

    class SciFiMachine
    {
        
private string f_name = null;

        
public string Name
        {
            
set
            {
                
if (value == null)
                {
                    
throw new NullReferenceException("Name cannot be null.");
                }
                
if (value == String.Empty)
                {
                    
throw new ArgumentException("Name cannot be empty.");
                }
                f_name 
= value;
            }
            
get
            {
                
if (f_name == null || f_name == String.Empty)
                {
                    
return "DefaultMachineName";
                }
                
else
                {
                    
return f_name;
                }
            }
        }

        
public SciFiMachine()
        {
            
// Some initializations
        }

        
public bool HasName()
        {
            
if (f_name == null)
            {
                
return false;
            }
            
if (f_name == string.Empty)
            {
                
return false;
            }
            
return true;
        }
        
//...
    }

 

这个类描述了一种机器,每台机器都可以有一个名字。对于这个名字对应的property的设计思路是,对于set,不允许设置为null或者为空;对于get则是返回机器的名字;当机器没有名字的时候,简单地返回默认的“DefaultMachineName”即可。

事实上,如果我们认真考虑的话,这个类的设计可能有若干不是很完善的地方;但是看看各种应用程序,包括MSDN上面的范例,很多都是这样的。
使用这个类的程序中,有这样一段:

 

 

            SciFiMachine m1 = new SciFiMachine();
            
//...
            if (! m1.HasName())
            {
                
m1.Name = GetNameFromUserInput();
            }
            
//...

 

 

这段程序的作用,显然是检查一台机器是否已经命名;如果还没有名字就让用户输入一个。这个类和这段程序在其原有的系统上运行正常;但是,当我们对其进行升级的时候,遇到了问题。

 

升级时,我们希望生成一个同名的机器。于是我们像这样修改了程序:

 

 

            SciFiMachine m1 = new SciFiMachine();
            SciFiMachine m2 
= new SciFiMachine();
            
//...
            m2.Name = m1.Name;
            
//...
            if (! m1.HasName())
            {
                m1.Name 
= GetNameFromUserInput();
            }

            
if (! m2.HasName())
            {
                m2.Name 
= GetNameFromUserInput();
            }
            
//...

 

 

上述的逻辑,如果是在C++世界里将不会有任何问题;可是在.Net的世界里,这却是一段会出错的程序。

比如,如果执行到m2.Name = m1.Name的时候,m1f_name还没有被赋值的话,m2f_name就会等于"DefaultMachineName";这样在下面验证是否有名字的时候,m1就会被GetNameFromUserInput()重新赋值,而m2则一直保持着"DefaultMachineName",根本不符合设计者的初衷。

 

造成这个问题的原因,当然有类的设计的问题、类的使用的问题;可是正如前面所说,像前者那样设计的类,很难说他设计错了;像后者那样使用,也不能说他完全用错了。这样,出现问题了也不大容易发现,因为我们已经习惯了在C++等语言中,使用a=b赋值之后ab一定相等的常识了。此外,在类的内部我们直接操作field,而在外部则通过property进行操作;虽然看起来语法相同,但是如果不分清是field还是property就乱用一气,是很容易造成错误的。

 

因此,在property的世界里,由于property的本质是一个函数而不是一块数据,因此property的世界里没有真正的赋值操作。形式上是A = B的操作,实际上是Set_A(Get_B())的操作;A = B未必一定能把B的值真正赋给A,使用property的时候必须牢记这一规律,谨慎使用。

(评论功能已被禁用)
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述
点击右上角即可分享
微信分享提示