荐读|属性与可直接访问的数据成员之间应该如何选

写在前面

在书写C#代码的时候你是否有过这样的经历:经常混用属性以及公有的数据成员。毕竟他们的用法基本一致,对于使用来说好像没什么区别啊。其实我也经常使用类的公有的数据成员来定义一些常量,为了简单,在一些仅仅需要对外暴露一些常量的类中(如定义一些全局使用的常量),也都是通过定义公有数据成员实现的。直到看到世界世界知名专家Bill Wagner的那本《More Effective C#》之后才意识到应该尽量“使用属性而不是可直接访问的数据成员”。因为属性具有修改的便捷性,多线程的支持等等。

作者:依乐祝
原文地址:https://www.cnblogs.com/yilezhu/p/11221447.html

为什么应该尽量使用属性

属性一直是C#语言的特色,目前的属性机制比C#刚引人它的时候更为完备,这使得开发者能够通过属性实现很多功能,例如,可以给gettersetter 设定不同的访问权限。与直接通过数据成员来编程的方式相比,自动属性可以省去大量的编程工作,而且开发者可以通过该机制轻松地定义出只读的属性。此外还可以结合以表达式为主体的 ( expression-bodied) 写法将代码变得更紧凑。 有了这些机制就不应该继续在类型中创建公有 ( publish) 字段, 也不应该继续手工编写getset方法。 属性既可以令调用者通过公有接口访问相关的数据成员 , 又可以确保这些成员得到面向对象式的封装。

注:在C#语言中, 属性这种元素可以像数据成员一样被访问, 但它们其实是通过方法来实现的。

方便修改

在所有的类与结构中,应该多使用属性,这样可以让你在发现新的需求时,更为方便的修改代码。比如说,如果你现在决定Customer类型的name(名字)数据不应该出现空白值,那么只需要修改Name属性的代码即可:

public class Customer
{
    private string name;
    public string name
    {
        get=>name;
        set
        {
            if(string.IsNullOrWhiteSpace(value))
            {
                throw new ArgumentException(
                    "Name connot be blank",
                    nameof(Name)
                );
            }
            name=value;
        }
    }
}

假如当初没有通过公有属性来实现Name,而是采用了公有数据成员,那么现在我们就必须在代码库里找到设置过该成员的每行代码,并逐个修改,这会浪费很多时间。

多线程支持

由于属性是通过方法实现的,因此,开发者很容易就能给它添加多线程的支持。例如可以像下面这样实现get与·set访问器,使外界对Name数据的访问得以同步:

public class Customer
{
    private object syncHandle = new object();

    private string name;
    public string name
    {
        get
        {
            lock (syncHandle)
            {
                return name;
            }
        }
        set
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                throw new ArgumentException(
                    "Name connot be blank",
                    nameof(Name)
                );
            }

            lock (syncHandle)
            {
                name = value;
            }
        }
    }
}

方法具备的好处,属性全有

C# 方法所具备的一些特性同样可以体现在属性身上,其中很明显的一条就是属性也可以声明为virtual:

public class Customer
{
    public virtual string Name
    {
        get;
        set;
    }
}

Note:刚才几个例子涉及属性的地方用的都是隐式写法。采用隐式写法时,开发者不用自己在属性的gettersetter中编写过多逻辑。也就是说,我们在用属性来表示比较简单的字段时,无需通过大量的模板代码来构建这个属性,编译器会为我们自动创建私有字段(该字段通常称为后援字段,并实现getset这两个访问器所需的简单逻辑)。

可以是抽象的,并成为接口的一部分

属性也可以是抽象的,从而成为接口定义的一部分,这种属性写起来与隐士属性相似。下面这段代码,就演示了怎样在泛型接口中定义属性。虽然与隐士属性的写法相似,但这种属性没有对应的实现物,定义该属性的接口只是要求实现本接口的类型都必须满足接口所订立的契约,也就是必须正确的提供NameValue这两个属性:

public interface INameValuePair<T>
{
    string Name { get; }
    T Value { get; set; }
}

很方便的控制获取及设置权限

对于类型中的属性来说,它的访问器分成getter(获取器)setter(设置器)这两个单独的方法,这使得我们能够对二者施加不同的修饰符,以便分别控制外界对该属性的获取权限以及设置权限。由于这两种权限可以分开调整,因此我们能够通过属性更为灵活的封装数据元素:

public class Customer
{
    public virtual string Name
    {
        get;
        protected set;
    }
}

带参数的属性

属性不只适用于简单的数字字段。如果某个类型要在其接口中发布能够用索引来访问的内容,那么就可以创建索引器。这相当于带有参数的属性,或者说参数化的属性。下面这种写法很有用,用它创建出的属性能够返回序列中的某个元素:

public class Customer
{
    public virtual string Name
    {
        get;
        protected set;
    }

    public int this[int index]
    {
        get => theValues[index];
        set => theValues[index] = value;
    }

    private int[] theValues = new int[100];
}
//Accessing an indexer;
int val=someCustomer[i];

此外,若参数是整数的一维索引器,则可以参与数据绑定,若参数不是整数的一维索引器,则可以用来定义映射关系:

private Dictionary<string, Address> addressValues;
    public Address this[string name]
    {
        get => addressValues[name];
        set => addressValues[name] = value;
    }

注意:索引器一律要用this关键字来声明。由于C#不允许给索引器起名字,因此同一个类型的索引器必须在参数列表上有所区别,否则就会产生歧义。
另外,索引器必须明确的实现出来,而不能像简单属性那样由系统默认生成。

其他说明

后期再把数据成员改成属性

尽管属性是个相当好的机制,可是还有人想先创建普通的数据成员,然后在确实有必要的情况下再将其替换成属性,以便使用属性所具备的优势。这种想法听上去很有道理,但实际并不合适。例如,如下定义一个普通数据成员的代码:

public class Customer
{
    public string Name;
}
string name = customerOne.Name;
customerOne.Name = "yilezhu";

其实我也经常这样用,不过都是定义一些静态的全局常量。
虽然在使用上属性可以像数据成员那样来访问,但是从MSIL的角度来看,却不是这样,因为访问属性时所使用的指令与访问数据成员所使用的指令是有区别的。因此如果把数据成员改成属性,则会破坏二进制层面的兼容机制,使得很难单独更新某一个程序集,需要全部更新。

属性的性能损耗

你可能要问了,是以属性的形式访问数据比较快,还是以数据成员的形式访问比较快?其实前者的效率虽然不会超过后者,但也未必落后于它。因为JIT编译器会对某些方法调用进行内联处理,其中也包括属性。如果编译器对属性进行内联处理的话,那么它的效率就会与数据成员相同。即便没有内联,两者的差别也可以忽略不计。

总结

今天给大家介绍了使用属性来访问数据成员的诸多优势,因此建议如果要在类型的公有或受保护的接口中发布数据,那么应该以属性的形式来发布,对于序列或字典来说,应该以索引器的形式发布。在日常的开发中虽然用属性的形式来封装变量会占用你一到两分钟的时间,但是如果你一开始没有使用属性,后来想用属性来设计,那么可能就得用好几个小时去修正了。现在多花点时间,将来会省很多功夫。
文章大多内容来自观看《More Effective C#》第一小节的内容所做的笔记,当然后续我还会对剩下的提升C#代码的50个方法进行总结记录,敬请期待吧。如果你有兴趣可以加DotNetCore实战项目交流群637326624跟大伙进行交流。

原文地址:https://www.cnblogs.com/yilezhu/p/11221447.html

时间: 2024-11-11 05:34:09

荐读|属性与可直接访问的数据成员之间应该如何选的相关文章

改善 C# 的语言习惯(一) - 使用属性而不是可访问的数据成员(整理中)

改善 C# 的语言习惯(一) - 使用属性而不是可访问的数据成员 序 为什么我们的程序运行得棒棒的,还要改呢?Why? 答:我们要让程序运行得更快,执行的效率更高,代码的可读性更强,维护的成本更低... .... 我们知道,守旧主义并不适用于计算机行业. 在这里,我会告诉你已经弃用或者应该弃用的东西,并告诉你推荐的用法,以及弃用的原因和推荐使用的原因.

使用属性替换可访问的数据成员

自1.0版本以来,c#对属性进行了一系列的增强,让其表达能力不断提高.你可以对setter和geter指定不同的访问权限.同时隐式属性也极大降低了声明属性的工作量,不会比声明数据成员麻烦多少.如果你还在类型中声明公有成员,那么快停下来吧,下面我们来对比一下两者的优缺点: 1,属性可以创建出类似于数据访问,但实际上却是方法调用的接口,所以它可以享受到方法调用的所有好处. 2,客户代码访问属性的时候,就像是在访问公有字段,不过其底层使用方法实现,其中还可以自由定义属性访问器的行为. 3,.Net F

在赋值运算符函数中,类的实例能直接访问私有数据成员???

印象中,private的数据成员只能在类的成员函数中使用,无法通过类的对象直接访问!(错误理解) 正确的理解是:在类的成员函数中,可以直接访问私有成员.即只要在该类的成员函数中,无论是直接访问该类的私有数据成员,还是访问某个对象(必选是该类型)的私有数据成员,均是可以的!!! 借鉴网上(http://blog.csdn.net/iaccepted/article/details/34853937 )的说法就是: 实践证明,类(class)私有成员可以被类成员函数访问,不区分成员在哪个实例(ins

【荐读】《我的前半生》:无论哪个阶层,这8条职场潜规则都终身受用

[荐读]<我的前半生>:无论哪个阶层,这8条职场潜规则都终身受用 2017-07-19人民日报 1 职场没有捷径 好走的路都不是坦途 剧: 做全职太太10年养尊处优的罗子君,早就和职场脱节,但为了与前夫争夺儿子的抚养权,罗子君咬牙开始找工作.然而作为一个30+的职场新人,子君自然到处碰壁,这让闺蜜唐晶十分担心,贺涵却如此安慰唐晶: "路要自己一步一步走,苦要自己一口一口吃,抽筋扒皮才能脱胎换骨.除此之外,没有捷径." 析: 很多人求之不得的捷径,其实不过是投机取巧,在苦难和

svn: E220001: 遇到不可读的路径;拒绝访问。

在客户端试图 svn merge 总是报svn: E220001: 遇到不可读的路径:拒绝访问.这个错误 提示 : SVN 遇到不可读的路径:拒绝访问. 英文是: Unreadable path encountered; access denied; 既然看不到日志又无法merge等操作. GOOGLE了一下,下面的方法解决了问题. 后面才发现是配置问题. 在项目的conf/svnserve.conf 中, 设置 anon-access = none 即可. 然后重启Subversion 服务.

【荐读】珍惜愿意批评你的人,那是你生命中的贵人

[荐读]珍惜愿意批评你的人,那是你生命中的贵人 2017-08-03人民日报 1 有人喜欢在街头算命,听到一句自己会有"贵人相助",心里往往会高兴好一阵子. 在许多人心底,大多都有一个贵人相助的祈盼,希望真的有那么一位贵人,能够动动小手指头,就一下子改变自己的命运,省去我们许多奋斗的时间和磨难. 只是,这样的贵人恐怕可遇而不可求,我们一辈子都难以遇到. 不过,在我们的生命里,却时时可能遇到另一种贵人,他能够让你的人生少走弯路,加快你成长的步伐,让你离成功越来越近. 这样的人,就是能够真

以对象的方式来访问xml数据表(二)

为什么要以对象的方式来访问xml数据表? 还记得,自己是在一次完成师兄布置的任务时接触到了xml,那时候需要用xml来作为数据文件,保存一个简单的图书管理系统的数据.于是就知道了,可以用xml文件来保存数据(而且比用简单的文本文件保存数据规范的多,在访问与读取数据上面都十分方便),就这样使用xml的征程开始了. 自己做的第一个WPF桌面应用程序——备忘录,就是用xml文件作为数据库.而且那个时候考虑到以后可能会经常使用到xml文件作为数据库,于是乎就写了一个专门用于访问xml文件的动态链接库,这

objective-C学习笔记(三)数据成员:属性与实例变量

类型成员 Type Member 结构体 struct 的成员很简单,只有变量. 类的成员就很多了: 数据成员 data member 描述对象(本讲重点) · 实例变量  instance variable · 属性 property 函数成员 function member · 方法 method · 初始化器  init · 析构器  dealloc 类的属性: 默认情况下,编译器会为属性定义propertyName自动合成: 一个getter访问器方法: propertyName 一个s

使用JavaScript访问XML数据

在本篇文章中,我们将讲述如何在IE中使用ActiveX功能来访问并解析XML文档,由此允许网络冲浪者操纵它们.这一网页将传入并运行脚本的初始化.你一定确保order.xml文档与jsxml.html在相同的相同的路径上.初始化部分将一个新的ActiveX对象例示为MSXML2.DOMDocument.3.0对象类型,然后脚本传入order.xml文档到内存中,并选择所有的/Order/Item节点.我们使用/Order/Item节点以识别文档已经包含的选项.文档中的标准有一个onLoad属性,这