找回密码
 立即注册
查看: 1143|回复: 20

C#中有了字段和方法,属性和索引是不是多此一举?

[复制链接]
发表于 2021-11-16 15:24 | 显示全部楼层 |阅读模式
用字段和方法,确实足以代替属性。只不过问题中的“多此一举”一词,有点偏向贬义词 :)
1、用字段和方法代替属性
比如一个简单的变量访问,外部需要读取名字,但不希望外部修改名字。用一个字符串保存名字很、用一个公开方法获取名字,很方便。
    class 学生
    {
        protected string name;
        public string GetName()
        {
            return name;
        }
    }
但是变量多了,到处是GetXXX() 并不舒服。用属性清爽多了:
    class 学生
    {
        public string Name { get; protected set; }
    }
如果不是单纯的get数据,而是要做一点处理,那么就不能省略变量定义。属性会退化成和函数差不多的写法:
    class 学生
    {
        protected string name;
        public string Name
        {
            get
            {
                return name + "弟弟";
            }
        }
    }
其实从以上例子看,属性并不是特别必须的东西,没它也行,有它更好。在Unity游戏开发中,我个人觉得属性可用可不用,到底用不用取决于团队的编码规范或个人习惯。
2、在WPF/UWP等程序开发中,属性变得很重要
个人觉得,属性真正的用武之地,是比较复杂的应用程序设计,特别是图形界面方面的开发。
现代图形界面开发,往往需要让变量(字段或属性)直接和界面元素绑定,比如string name直接绑定到界面上的标签,这样给name赋值,界面就会变。



比如搜索框里的文字,直接对应到某个变量。t=“abc”,搜索框里就变成abc

这样就带来一个问题,需要对成员变量name进行简单的封装。在修改它的值时,界面也跟着刷新内容。
而且更进一步,这种变量的绑定还有各种具体情况,有只写的、只读的、会主动变的、只会跟着别的字段变化而变的(依赖性的)
要想满足这些需求,并且让程序员用着方便,就得封装一些合适的写法,否则一大堆代码看着太头疼了。
所以属性在较复杂的GUI框架设计中变得十分有用,不可或缺。缺了的话可会让代码膨胀很多而且看着很乱。
比如在网上随便摘抄了一段WPF程序:
// 1. 使类型继承DependencyObject(依赖性的对象)
    public class Person : DependencyObject
    {
        // 2. 声明一个静态只读的DependencyProperty 字段
        public static readonly DependencyProperty nameProperty;
      
        static Person()
        {
            // 3. 注册定义的依赖属性
            nameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Person),
                new PropertyMetadata("Learning Hard",OnValueChanged));
        }

        // 4. 属性包装器,通过它来读取和设置我们刚才注册的依赖属性
        public string Name
        {
            get { return (string)GetValue(nameProperty); }
            set { SetValue(nameProperty, value); }
        }

        private static void OnValueChanged(DependencyObject dpobj, DependencyPropertyChangedEventArgs e)
        {
            // 当只发生改变时回调的方法
        }

    }
虽然看起来繁琐一些,但是这样写的属性可以和界面元素互动,还能添加各种响应事件,实际上节约了很多工作量。

总结:如果在自己写的代码中,用属性或者用字段效果差不多,那完全可以不用。而在某些框架比如WPF中,不得不用属性。
千万不要为了用属性而用属性,完全没必要。但也不需要怀疑它存在的意义。
C#中有大量日常用不到的特性和关键字,但它们确实是为了实际应用而设计的。只不过在不同的开发领域,常用的特性不相同而已。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2021-11-16 15:30 | 显示全部楼层
用字段和方法,确实足以代替属性。只不过问题中的“多此一举”一词,有点偏向贬义词 :)
1、用字段和方法代替属性
比如一个简单的变量访问,外部需要读取名字,但不希望外部修改名字。用一个字符串保存名字很、用一个公开方法获取名字,很方便。
    class 学生
    {
        protected string name;
        public string GetName()
        {
            return name;
        }
    }
但是变量多了,到处是GetXXX() 并不舒服。用属性清爽多了:
    class 学生
    {
        public string Name { get; protected set; }
    }
如果不是单纯的get数据,而是要做一点处理,那么就不能省略变量定义。属性会退化成和函数差不多的写法:
    class 学生
    {
        protected string name;
        public string Name
        {
            get
            {
                return name + "弟弟";
            }
        }
    }
其实从以上例子看,属性并不是特别必须的东西,没它也行,有它更好。在Unity游戏开发中,我个人觉得属性可用可不用,到底用不用取决于团队的编码规范或个人习惯。
2、在WPF/UWP等程序开发中,属性变得很重要
个人觉得,属性真正的用武之地,是比较复杂的应用程序设计,特别是图形界面方面的开发。
现代图形界面开发,往往需要让变量(字段或属性)直接和界面元素绑定,比如string name直接绑定到界面上的标签,这样给name赋值,界面就会变。



比如搜索框里的文字,直接对应到某个变量。t=“abc”,搜索框里就变成abc

这样就带来一个问题,需要对成员变量name进行简单的封装。在修改它的值时,界面也跟着刷新内容。
而且更进一步,这种变量的绑定还有各种具体情况,有只写的、只读的、会主动变的、只会跟着别的字段变化而变的(依赖性的)
要想满足这些需求,并且让程序员用着方便,就得封装一些合适的写法,否则一大堆代码看着太头疼了。
所以属性在较复杂的GUI框架设计中变得十分有用,不可或缺。缺了的话可会让代码膨胀很多而且看着很乱。
比如在网上随便摘抄了一段WPF程序:
// 1. 使类型继承DependencyObject(依赖性的对象)
    public class Person : DependencyObject
    {
        // 2. 声明一个静态只读的DependencyProperty 字段
        public static readonly DependencyProperty nameProperty;
      
        static Person()
        {
            // 3. 注册定义的依赖属性
            nameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Person),
                new PropertyMetadata("Learning Hard",OnValueChanged));
        }

        // 4. 属性包装器,通过它来读取和设置我们刚才注册的依赖属性
        public string Name
        {
            get { return (string)GetValue(nameProperty); }
            set { SetValue(nameProperty, value); }
        }

        private static void OnValueChanged(DependencyObject dpobj, DependencyPropertyChangedEventArgs e)
        {
            // 当只发生改变时回调的方法
        }

    }
虽然看起来繁琐一些,但是这样写的属性可以和界面元素互动,还能添加各种响应事件,实际上节约了很多工作量。

总结:如果在自己写的代码中,用属性或者用字段效果差不多,那完全可以不用。而在某些框架比如WPF中,不得不用属性。
千万不要为了用属性而用属性,完全没必要。但也不需要怀疑它存在的意义。
C#中有大量日常用不到的特性和关键字,但它们确实是为了实际应用而设计的。只不过在不同的开发领域,常用的特性不相同而已。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2021-11-16 15:31 | 显示全部楼层
对于高手来说是多此一举,任何语言只是工具而已都一样的,心中有剑就行,手里有无没分别。但对于我等又懒智商又低的人来说,属性和索引都是精简代码的利器。例如一下C#代码
persons.Age++;
对应的Java代码是:
persons.get(i).setAge(persons.get(i).getAge()+1);代码量差不多是3倍,对我等懒人很折磨。


另外我觉得没有属性的话,另一个问题是在Attribute(Java管这叫注解)上,很多初学者搞不清一个Attribute是应该放到get、set还是变量上,我的另一个回答也有说到这方面的问题。
C# 从语法角度比 Java 优秀在哪里?
发表于 2021-11-16 15:32 | 显示全部楼层
这个问题得从两个层面回答,封装和方法本身。先来说一下封装。
属性就是用来包装字段、为字段添加额外处理逻辑和封装逻辑的
举个例子,你的鼻子和嘴巴都能呼吸,但是嘴还能吃饭,那么嘴的功能相较于鼻子更完备,那干脆都用嘴巴呼吸算了,何必用鼻子呢?实际上鼻子封装了呼吸的逻辑,保证吸入空气里的灰尘能降到最小,这就是封装给我们带来的用途。如果我们全凭靠嘴巴呼吸的话,确实鼻子没有用,但这对我们人的身体会带来潜在的危险,诸如吸入灰尘沾到肺上之类的。
属性和索引器就是这样的一种存在,专门封装字段的处理逻辑,以防别的程序员调用时乱使用字段,给字段处理加上任意执行内容,以至于出现危险的情况。
// 字段。
private int _age;

// 上面这个字段配套的属性。
public int Age
{
    get => _age;
    set
    {
        // throw 表达式。可以内嵌 throw 语句到 ?: 和 ?? 运算符之中。
        // 虽然看起来 throw 表达式不返回东西,但这样书写就是为了简化
        // 不必要的 if-else 语句。
        _age = value < 0 || value > 150
            ? throw new ArgumentOutOfRangeException()
            : value;
    }
}

public int _age; // 这个是字段,但用的是 public。
显然我们就能看出区别。没有属性,我们就可以给年龄赋值为任意数值,甚至负数也没问题。万一有些时候处理逻辑里用到 age 数值的时候出现了各种奇葩结果,那你这个时候再来反推出是传参的 bug,我估计就比较麻烦了。
索引器也是一样的道理,不过索引器侧重于集合对象使用。如果一个对象专门用来存储一系列元素,那么这种对象就有可能需要索引器的支持。和前文的思维一样,如果你没有索引器进行索引的验证,那么赋的值就可能更加随意,使得出现意外情况。
实际上还有一个封装的东西:事件。事件是委托字段的封装,委托字段用于执行内部对象的特定时刻触发的行为。当我们直接把对象里的委托字段改为 public 时,别人就可以给委托字段赋值为任意同样委托类型的执行操作,使得行为变更,相当于“截胡”。
事件的存在就是为了解决封装。事件只允许我们可以对对象添加和删除指定的处理逻辑。而内部的委托字段不允许我们从外部直接使用它们。这样的话,我们从外部调用对象的话,就只可以为对象添加新的处理逻辑,或者移除掉添加过的处理逻辑了。这样比起原本直接访问委托字段,为其设置处理逻辑算是迂回了一下,但起到封装的机制,防止我们篡改原定的逻辑。
所以:建议书写格式为:
// 假如外部有一个用于某个事件处理的委托。
public delegate void CustomEventHandler(object sender, CustomEventArgs e);

// 代入到对象里作为委托字段存在。
public class Class
{
    private CustomEventHandler _customEventHandler;

    public event CustomEventHandler CustomEvent
    {
        add => _customEventHandler += value;
        remove => _customEventHandler -= value;
    }
}
或者用高级的语法,省略 add 和 remove 块。
// 假如外部有一个用于某个事件处理的委托。
public delegate void CustomEventHandler(object sender, CustomEventArgs e);

// 代入到对象里作为委托字段存在。
public class Class
{
    public event CustomEventHandler CustomEvent;
}
实际上这两种写法有一点不同,你可以参看 Sharplab.io 网站的生成代码。后者处理的内容会更多一些。
所以请记住,属性、索引器和事件专门为普通字段和委托字段提供封装。这是侧重封装层面解答的第一个问题。
第二个问题,方法。方法确实可以解决所有属性、索引器的赋值行为,因为只要我们有了方法,就可以书写类似于 getAge 和 setAge 的方法,这样依然和属性具有等同效果。之所以 C# 出了一个属性机制(还有索引器),实际上是为了简化书写代码和规范化处理逻辑,将类似于 xiaoMing.setAge(20) 这种代码改写为 xiaoMing.Age = 20 更为直观和更具有可读性的赋值语句形式。索引器也是一样,有些时候用 get 方法获取一个集合对象内部的某一个索引位置的元素时,用索引器写起来的样子比起方法调用形式要更具可读性(尽管你看方法看习惯了,但是带有看起来像是数学符号的这些操作符,总能让我们更清晰一些,只要这些操作符得是我们常见的符号)。
顺带说一下,xiaoMing.Age = 20 这个语句是给 Age 属性赋值为 20,并非直接操作了 _age 字段。在这个等号的赋值行为中,我们可以加入类似于前面的数据验证,如果超过范围就产生异常,于是整个等号的意义就发生了变化(它不仅仅有赋值行为,还有验证行为),所以这也是一个重要的知识点。
发表于 2021-11-16 15:37 | 显示全部楼层
有了字段和方法确实可以涵盖属性和索引器的全部功能,因为C#的属性/索引器本质上就是JavaBeans的getter/setter方法。但是C#的属性可以大大减少代码量。
关键是要在设计类的时候选择符合逻辑的方法。比如有些集合类(如Json Object)就是拥有和List/Dictionary相似的使用方法,那么设计成索引器就更符合使用习惯。这方面Java的一个典型反例就是String的charAt()。鉴于string本身就是个字符串数组,使用索引器取字符显然比用charAt对于调用者来说更直观友好。
<hr/>虽然现在.NET圈子一说起属性就喜欢把JavaBeans拿出来嘲讽一下,但是我想重点想讲一讲,C#的属性本身也有问题。这个问题的来源就是它的语法和公有字段是完全相同的。这样的设计让C#代码在没有IDE帮助的时候无法分辨一段代码究竟是用了字段还是属性。把属性完全和字段同等对待的程序员也会犯一些意想不到的错误:
    属性可以抛出异常,但是字段不会属性不能用于ref 和 out参数读写属性的顺序可能导致结果不同。甚至你什么都不做,DateTime.Now这样的属性也会在每次调用的时候都返回不同的值,字段从来不会有这样的行为。而在没有IDE的帮助下,你无法知道你调用的这个【不知道是字段还是属性】的东西到底会有什么行为属性返回的值也许和对象的状态没有任何关系
虽然用getter和setter方法也会有上面的行为,但是根本就不会有程序员把getter和setter方法当作字段来用啊。尤其对于第二条,相信相当多.NET程序员会试图用给函数的ref参数传入一个属性,发现语法上根本不支持,才意识到两者还有这种区别。

但是如果单纯把属性当作方法来看待,也不行。
一个最明显的问题就是C#到8.0为止只有拓展方法,没有拓展属性。这就导致了【List等集合的Count是属性,但是用Linq时候才发现IEnumerable的Count()是方法】这样分裂的情况。
发表于 2021-11-16 15:38 | 显示全部楼层
有一台福特代步,不代表我不能再有一台法拉利炸街(没睡醒)
发表于 2021-11-16 15:41 | 显示全部楼层
C# 的操作符重载本质上也是方法调用,那操作符是不是多此一举呢?保持读写代码的直觉还是挺重要的,至少用起来心情好
发表于 2021-11-16 15:45 | 显示全部楼层
属性和索引本身就是方法。你要就功能来说,那就是多此一句,因为这不会增加什么功能。
但是“有了方法为什么还要有属性和索引”,就和“为什么不用汇编,要用C#”是一个道理。
发表于 2021-11-16 15:55 | 显示全部楼层
其实一点也不重复,字段和方法本质上是属于不同的东西,一个属于状态信息一个属于行为。
然后属性和索引是对c#更加精细的控制,比如字段:public string name; 你就不能控制读和写的权限,但是属性就不一样了:public string name {get; private set;}  这样就能把c#面向对象的概念写的更加合理
发表于 2021-11-16 15:57 | 显示全部楼层
语法糖,不仅有利于提升编程体验,还可以提供语言层面的抽象机制。
所以C#很甜啊,隔壁Java老咸了。
C#的糖还有很多(比如Linq,比如拓展方法),属性和索引只是基本的糖。
引申一下,机器语言的能力也是足够完备的,为什么我们需要汇编语言作“助记”呢?汇编语言是足够完备的,为什么我们需要通用编程语言(比如C)呢?C语言是足够完备的,为什么我们需要更复杂更抽象的C++呢?
让我们花更少的时间,做更多的事。
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-15 22:36 , Processed in 0.095457 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表