1.1 - C#语言习惯 - 使用属性而不是可访问的数据成员

简介:   属性一直是C#语言中的一等公民。自1.0版本以来,C#对属性进行了一系列的增强,让其表达能力不管提高。你甚至可以为setter和getter指定不同的访问权限。   隐式属性也极大降低了声明属性时的工作量,不会比声明数据成员麻烦多少。

 

  属性一直是C#语言中的一等公民。自1.0版本以来,C#对属性进行了一系列的增强,让其表达能力不管提高。你甚至可以为setter和getter指定不同的访问权限。

  隐式属性也极大降低了声明属性时的工作量,不会比声明数据成员麻烦多少。

 

  若你仍然在类型中声明公有成员,或是仍在手工编写set或get之类的方法,那么快停下来吧。

  属性允许将数据成员作为公共接口的一部分暴露出去,同时仍旧提供面向对象环境下所需要的封装。

  属性这个语言元素可以让你像访问数据成员一样使用,但其底层依旧使用方法实现。

 

  类型的某些成员确实非常适合作为数据,例如某个客户的名称,某个站点的x、y坐标或上一年度的收入等。

  而属性则让你可以创建出类似于数据访问,但实际上却是方法调用的借口,自然也可以享受到方法调用的所有好处。

  客户代码访问属性时,就像是在访问公有的字段,不过其底层使用方法实现,其中可以自由定义属性访问器的行为。

 

  .NET Framework假设你会对公有数据成员使用属性。

  实际上,.NET Framework中的数据绑定类仅支持属性,而不支持公有数据成员。

  对于类所有的数据绑定类库均是如此,包括WPF、WinForms和Silverlight。数据绑定会将某个对象的一个属性和某个用户界面控件相互关联起来。

  数据绑定机制将使用反射来找到类型中的特定属性:

1 textBoxCity.DataBindings.Add("Text", address, "City");

  这段代码将textBoxCity控件的Text属性绑定到了address对象的City属性上。

  公有的数据成员并不推荐使用,因此Framework Class Library设计其也不支持其实现绑定。这样的设计也保证了你必须选择合适的面向对象技术。

 

  确实,数据绑定只是用在用户界面逻辑中会使用到的类中。但这并不意味着属性仅应该用在UI逻辑中,其他类和结构中也应使用属性。

  在日后产生新的需求或行为时,属性更易于修改。

  例如,你会很快有这样的想法,客户对象不应该有空白的名称。若你使用了公有属性来封装Name,那么只要修改一处即可:

 1     public class Customer
 2     {
 3         private string name;
 4         public string Name
 5         {
 6             get { return name; }
 7             set
 8             {
 9                 if (string.IsNullOrEmpty(value))
10                 {
11                     throw new ArgumentException("Name cannot be blank!", "Name");
12                 }
13                 name = value;
14             }
15         }
16     }

 

  若是使用了公有的数据成员,那么就需要查找每一处设置客户名称的代码并逐一修复。这将花费大量的时间。

 

  因为属性是使用方法来实现的,所以添加多线程支持也非常简单。

  很容易即可在属性的get和set访问器中作如下的修改,从而支持对数据的同步访问:

 1     public class Customer
 2     {
 3         private object syncHandle = new object();
 4 
 5         private string name;
 6         public string Name
 7         {
 8             get
 9             {
10                 lock (syncHandle)
11                 {
12                     return name;
13                 }
14             }
15             set
16             {
17                 if (string.IsNullOrEmpty(value))
18                 {
19                     throw new ArgumentException("Name cannot be blank!", "Name");
20                 }
21                 lock (syncHandle)
22                 {
23                     name = value;
24                 }
25             }
26         }
27     }

 

  属性可以拥有方法的所有语言特性。例如,属性可以为虚的(virtual):

1     public class Customer
2     {
3         public virtual string Name
4         {
5             get;
6             set;
7         }
8     }

  注意,上述例子中使用了C# 3.0中的隐式属性语法。使用属性来封装私有字段是一个常用的模式。

  通常而言,我们并不需要验证属性的getter或setter逻辑。

  因为语言本身提供了简化的隐式属性语法,力求尽量降低开发人员的输入工作,即将一个简单的字段暴露或属性。

  编译器将为你创建一个私有的成员字段,并自动生成最简单的get和set访问器的逻辑。

 

  你还可以将属性声明为抽象的(abstract),以类似隐式属性语法的形式将其定义在接口中。

  下面的例子就将属性定义在了一个泛型接口中。

  需要注意的是,虽然其语法和隐式属性完全相同,但是编译器却不会自动生成任何实现。

  接口只是定义了一个契约,强制所有实现了该接口的类型都必须满足。

 1     public interface INameValuePair<T>
 2     {
 3         string Name
 4         {
 5             get;
 6         }
 7 
 8         T value
 9         {
10             get;
11             set;
12         }
13     }

 

  属性是一种全功能的、第一等的语言元素,能够一方法调用的形式访问或修改内部数据。成员函数中可以实现的功能均可在属性中实现。

 

  属性的访问器将作为两个独立的方法变异到你的类型中。在C#中,你可以为get和set访问器制定不同的访问权限。

  这样即可更精妙的控制作为属性暴露出来的数据成员的可见性。

 

1     public class Customer {
2         public virtual string Name
3         {
4             get;
5             protected set;
6         }
7     }

  上述属性语法的表达含义圆圆超出了简单数据字段的范畴。

  若类型需要包含并暴露出可索引的项目,那么可以使用索引器(即支持参数的属性);若想反悔序列中的项,创建一个属性会是个不错的做法。

 1     public class User
 2     {
 3         private string name;
 4 
 5         public string Name
 6         {
 7             get { return name; }
 8             set { name = value; }
 9         }
10 
11         // 获取Name中的指定字符
12         public char this[int index]
13         {
14             get
15             {
16                 if (index < 0)
17                 {
18                     throw new ArgumentException("Index must be more than 0!", "index");
19                 }
20                 return name[index];
21             }
22         }
23     }
24     class Program
25     {
26         static void Main(string[] args)
27         {
28             User mrZhang = new User() { Name = "张董" };
29 
30             Console.WriteLine(mrZhang[0]);
31         }
32     }
索引器(支持参数的属性)

  运行结果:

 

  索引器和单一条目属性有着同样的语言支持:他们都是作为方法实现的,因此可以在索引器内部实现任意的验证或计算逻辑。

  索引器也可以为虚的或抽象的,可声明在接口中,可以为只读或读写。

 

  一维且使用数字作为参数的索引器也可以参与数据绑定。使用非整数的索引器可用来定义Map和Dictionary:

1 public Address this[string name]
2 {
3 get { return addressValues[name]; } 4 set { addressValues[name] = value; } 5 }

 

 

  C#中支持多维数组,类似的,我们也可以创建多维索引器,每一个维度上可以使用同样或不同的类型:

1 public int this[int x, int y]
2 {
3     get { return ComputeValue(x, y); }
4 }
5 
6 public int this[int x, string name]
7 {
8     get { return ComputeValue(x, name); }
9 }

 

  需要注意的是,所有的索引器都是用this关键字声明,C#不支持为索引器命名。

  因此,类型中每个不同的索引器都必须有不同的参数列表,一面混淆。

  几乎属性上的所有特性都能应用到索引器上。索引器也可为虚的或抽象的,可以对setter和getter给出不同的访问限制,不过却不能像属性那样创建隐式索引器。

 

  属性的功能很强大,是个不错的改进。

  但你是不是还在想能不能先用数据成员来实现,而在稍后需要其他各种功能的时候再改成属性呢?

  这看似是个不错的策略,不过实际上却行不通。

  考虑如下这个类的定义:

1 // using public data members, bad practice
2 public class Customer
3 {
4     public string Name;
5     // remaining implementation omitted
6 }

 

  这个类描述一个客户(Customer),包含了一个名称(Name)。你可以使用熟悉的成员表示方法获取或设置该名称:

1 Customer zhangDong = new Customer();
2 zhangDong.Name = "张董";
3 string name = zhangDong.Name;

 

  看似简单直观,你也会认为若是日后将Name改成属性,那么代码也可以无需修改保持正常。

  但这个答案并不是完全正确的。

  属性仅仅是访问时类似于数据成员,这是语法所实现的目的。

  不过属性并不是数据,属性的访问和数据的访问将会生成不同的MSIL(Microsoft Intermediate Language,微软中间语言)指令。

 

  虽然属性和数据成员在源代码层次上是兼容的,不过在二进制层面上却大相径庭。

  这也就意味着,若将某个公有的数据成员改成了与之等同的共有属性,那么久必须重新编译所有用到该共有数据成员的代码。

  C#把二进制程序及作为一等公民看待。该语言本身的一个目标就是支持发布某个单一程序集时,不需要更新整个的应用程序。

  而这个将数据成员改为属性的简单操作却破坏掉了二进制兼容性,也就会让更新单一程序集变得非常困难。

 

  若是查看属性生成的IL,那么你或许会想比较一下属性和数据成员的性能。

  属性当然不会比数据成员访问快,不过也不会比其慢多少。

  JIT编译器将内联一些方法调用,包括属性访问器。当JIT编译器内联了属性访问器时,数据成员和属性的访问效率即可持平。

  即使某个属性访问器没有被内联,其性能差距也实在是微乎其微,仅仅一次函数调用之别而已。只有在某些极端情况下,二者的差距才会有所影响。

  

  在调用方法看,属性虽然是方法,但它和数据却有着类似的概念。这会使你的调用者对属性有着一些潜意识的认识。

  例如,调用者会把属性访问当成是数据的访问。

  不管怎样,二者看上去很像描述性访问器应该满足这些潜意识的预期。

  get访问器不应该有可被观察到的副作用。set访问器会修改状态,用户应该可以看到调用后带来的改变。

 

  调用者也会对属性访问器的性能有着一定的预期。

  属性的访问就像是访问一个数据字段,因此不会与访问数据由太过明显的性能差别。

  属性访问器不应该执行长时间的计算,或进行跨应用的调用(例如执行数据库查询等),或是其他任何与调用者期待不符的耗时操作。

 

  无论何时需要在类型的共有或保护接口中暴露数据,都应该使用属性。你也应该使用索引器来暴露序列或字典。

  所有的数据成员都应该是私有的,没有任何例外。

  这样你就立即得到了数据绑定的支持,也便于日后瑞方法实现的各种修改。

  对于将任何变量封装到一个属性所需的额外输入工作其实不会占用太多时间,而日后若是需要使用属性来更正设计,则会花去大量的时间。

  现在多投入一点点,换来的是今后维护时的更加游刃有余。

 

【来自:张董'Blogs:http://www.cnblogs.com/LonelyShadow,转载请注明出处。】

亲们。码字不容易,觉得不错的话记得点赞哦。。

目录
相关文章
|
1月前
|
开发框架 .NET C#
C#数据去重的这几种方式,你知道几种?
C#数据去重的这几种方式,你知道几种?
|
2月前
|
SQL 开发框架 .NET
EntityFramework数据持久化复习资料3、C#拓展方法与yield关键字使用
EntityFramework数据持久化复习资料3、C#拓展方法与yield关键字使用
22 0
|
3月前
|
存储 安全 编译器
C# 11.0中的泛型属性:类型安全的新篇章
【1月更文挑战第23天】C# 11.0引入了泛型属性的概念,这一新特性为开发者提供了更高级别的类型安全性和灵活性。本文将详细探讨C# 11.0中泛型属性的工作原理、使用场景以及它们对现有编程模式的改进。通过深入了解泛型属性,开发者将能够编写更加健壮、可维护的代码,并充分利用C#语言的最新发展。
|
3月前
|
编译器 数据处理 C#
C#中的异步流:使用IAsyncEnumerable<T>和await foreach实现异步数据迭代
【1月更文挑战第10天】本文介绍了C#中异步流的概念,并通过使用IAsyncEnumerable<T>接口和await foreach语句,详细阐述了如何异步地迭代数据流。异步流为处理大量数据或需要流式处理数据的场景提供了一种高效且非阻塞性的方法,使得开发者能够更优雅地处理并发和数据流问题。
|
3月前
|
存储 数据管理 开发工具
Baumer工业相机堡盟工业相机如何通过NEOAPI SDK设置相机本身的数据保存(CustomData)功能(C#)
Baumer工业相机堡盟工业相机如何通过NEOAPI SDK设置相机本身的数据保存(CustomData)功能(C#)
26 0
|
30天前
|
SQL C# 数据库
C# 读取多条数据记录导出到 Word 标签模板
C# 读取多条数据记录导出到 Word 标签模板
|
30天前
|
安全 数据处理 C#
C# Post数据或文件到指定的服务器进行接收
C# Post数据或文件到指定的服务器进行接收
|
1月前
|
Java C#
C#学习相关系列之多线程(七)---Task的相关属性用法
C#学习相关系列之多线程(七)---Task的相关属性用法
|
1月前
|
数据采集 存储 C#
抓取Instagram数据:Fizzler库带您进入C#程序的世界
在当今数字化的世界中,数据是无价之宝。社交媒体平台如Instagram成为了用户分享照片、视频和故事的热门场所。作为开发人员,我们可以利用爬虫技术来抓取这些平台上的数据,进行分析、挖掘和应用。本文将介绍如何使用C#编写一个简单的Instagram爬虫程序,使用Fizzler库来解析HTML页面,同时利用代理IP技术提高采集效率。
抓取Instagram数据:Fizzler库带您进入C#程序的世界
|
1月前
|
数据挖掘 C# 开发工具
采用C#语言开发的全套医院体检系统PEIS源码功能介绍
体检系统,是专为体检中心/医院体检科等体检机构,专门开发的全流程管理系统,通过软件实现检测仪器数据自动提取,内置多级医生工作台,细化工作将体检检查结果汇总,生成体检报告登记到计算机系统中。通过软件系统进行数据分析统计与评判以及建立体检相关的体检档案。从而实现体检流程的信息化,提高工作效率,减少手动结果录入的一些常犯错误。 在实际应用中,医院体检系统能够解决传统体检中手工操作带来的问题,如工作量大、效率低下、易漏检、重检或错检等。通过与医院信息系统(如HIS、LIS、PACS等)的连接,系统能够满足体检中心的日常工作流程,提供更好的管理、统计和查询分析功能。同时,基于网络基础的系统可以在网上传输
24 1