第十五章 属性

1.属性
属性是一组get/set访问器方法,封装类中的字段和数据。

常规封装字段:

class ApplePie
{
    private int sugar;

    public int GetSugar()
    { 
        return this.sugar;
    }
    
    public void SetSugar(int sugar)
    {
        this.sugar = sugar;
    }
}

public static void Main(string [] args)
{
    int sugar = 5;
    ApplePie applePie = new ApplePie();

    // get访问器读取字段,set访问器设置字段
    applePie.SetSugar(sugar);
    int getSugar = appplePie.GetSugar();
}

使用get/set方法虽然保护了字段,但也付出了代价。有没有什么方法既可以保护字段,又可以像直接访问字段那样简便的操作?
使用属性:

class ApplePie
{
    private int _sugar;

    public int Sugar
    {
        get{ return this._sugar;}
        set{ this._sugar = value;} // value为set访问器内置的参数,传递要写入的数据。

        // 也可以使用表达式主体方法 =>
        //get => return this._sugar;
        //set => this._sugar = value;
    }
}

public static void Main(string [] args)
{
    int sugar = 5;
    ApplePie applePie = new ApplePie();

    applePie.Sugar = sugar;
    applePie.Sugar ++;
    
}

2.属性的局限性
属性在外观、行为上感觉上都像字段。但属性本质上是方法。此外,属性存在以下限制:
a, 只有在结构或类初始化好之后,才能通过该结构或类的属性来赋值。

ApplePie applePie;
applePie.Sugar = 10; // 编译时错误,applePie未赋值,Sugar本质上是ApplePie的一组get/set方法。

b, 不能将属性作为ref或out参数传给方法;但可写的字段能作为ref或out参数传递。
这是由于属性并不真正指向一个内存位置;相反,它指向的是一个访问器方法。

Bake(ref applePie.Sugar); // 编译时错误

c, 属性最多只能包含一个get和一个set访问器。不能包含其它方法、字段或属性。
d, get和set访问器不能获取任何参数。要赋的值会通过内建的、隐藏的value变量自动传递给set访问器。
e, 不能声明const属性。

const int Flour 
{
    get => ....  
    set => ....   // 编译时错误
}

3.自动属性

class Pizza
{
    public bool Drink { get; set;}
}

// C#编译器自动将这个定义转换成私有字段以及一个默认的实现。换句话说,这个自动属性等价于以下形式:
class Pizza 
{
    private bool _drink;
  
    public bool Drink
    {
        get { return this_drink; }
        set { this._drink = value; }
    }
}

// 虽然形式上等价,但手动写的字段和编译器转换自动属性的私有字段不互通!
class Wine
{
    private int _cup;

    public int Cup { get;set;} 

    // 手动关联
    public int CupBig
    {
        get => this._cup;
        set => this._cup = value;
    }
    public void Output()
    {
        Console.WriteLine($"_cup: {_cup}, Cup: {Cup}");
    }

    
}

public static void Main(string [] args)
{
    Wine w = new Wine();
    w.Cup = 10;
    w.Output();
    // 输出:
    //     _cup: 0, Cup: 10
    
    w.CupBig = 11;
    w.Output();
    // 输出:
    //     _cup: 11, Cup: 11    
}

4.用属性初始化对象

class Kalakukko // 鱼馅饼
{
    private bool _fish;
    private bool _sauce;
    private bool _beer;
  
    public Kalakukko()
    {
        this._fish = this._sauce = this._beer = false;
    }

    public Kalakukko(bool fish)
    {
        this._fish = fish;
        this._sauce = this._beer = false;
    }

    public Kalakukko(bool fish, bool sauce)
    {
        this._fish = fish;
        this._sauce = sauce;
        this._beer = false;
    }
    
    public Kalakukko(bool fish, bool sauce, bool beer)
    {
        this._fish = fish;
        this._sauce = sauce;
        this._beer = beer;
    }

    // 如何为鱼馅饼类的字段写不同组合的构造器 ?
    // 1.使用可选参数
    //public Kalakukko(bool fish = false, bool sauce = false, bool beer = false)
    //{
    //    this._fish = fish;
    //    this._sauce = sauce;
    //    this._beer = beer;
    //}
}

public static void Main(string [] args)
{
    Kalakukko kalakukko = new Kalakukko(true, true);
    Kalakukko kalakukko2 = new Kalakukko(true, beer: true); // 传递具名参数(假设使用可选参数)。
}

一个更好、更透明的方式是将私有变量初始化为一组默认值,并将它作为属性公开:

class KalakukkoInProperty
{
    private bool _fish = false;
    private bool _sauce = false;
    private bool _beer = false;

    public KalakukkoInProperty(bool fish) 
    {
        this._fish = fish;
    }

    public bool Fish
    {
        set => this._fish = value;
    }

    public bool Sauce 
    {
        set => this._sauce = value;
    }

    public bool Beer
    {
        set => this._beer = value;
    }
}

public static void Main(string [] args)
{
    KalakukkoInProperty kalakukkoIn = new KalakukkoInProperty(true)
                                    { Fish=true, Sauce = true, Beer = false};
    // 先运行构造器,再对属性进行设置
}
posted @ 2021-03-09 13:10  葡式蛋挞  阅读(12)  评论(0)    收藏  举报