(原创)c#学习笔记10--定义类成员01--成员定义03--定义属性

10.1.3  定义属性

  属性定义方式与字段类似,但包含的内容比多。如前所述,属性涉及的内容比字段多,是因为它们在修改状态前还可以执行一些额外的操作。实际上,它们可能并不修改状态。属性拥有两个类似于函数的块,一个块用于获取属性的值,另一个块用于设置属性的值。

  这两个块也称为访问器,分别用get和set关键字来定义,可以用于控制对属性的访问级别。可以忽略其中的一个块来创建只读或只写属性(忽略get块创建只写属性,忽略set块创建只读属性)。当然,这仅适用于外部代码,因为类中的其他代码可以访问这些代码块能访问的数据。还可以在访问器上包含可访问修饰符,例如使get块变成公共的,把set块变成受保护的。只有包含其中一个一个块,才能获得有效属性(既不能读取也不能修改的属性没有任何用处)。

  属性的基本结构包括标准的可访问修饰符(public、private),后跟类名、属性名和 get 块(或 set块,或者get块和set块,其中包含属性处理代码),例如:

public int MyIntProp {
    get {
        // Property get code.
    }
    set {
        // Property set code.
    }
}

  定义代码中的第一行非常类似于定义字段的代码。区别是行末没有分号,而是一个包含嵌套get和set块的代码块。

  get块必须有一个属性类型的返回值,简单的属性一般与私有字段相关联,以控制对这个字段的访问,此时get块可以直接返回该字段的值,例如:

// Field used by property
private int myInt;

// Property
public int MyIntProp {
    get {
        return myInt;
    }
    set {
        // Property set code.
    }
}

  类外部的代码不能直接访问这个 myInt 字段,因为其访问级别是私有的。外部的代码必须使用属性来访问该字段。set函数以类似的方式把一个值赋给字段。这里可以使用关键字value表示用户提供的属性值:

// Field used by property
private int myInt;

// Property
public int MyIntProp {
    get {
        return myInt;
    }
    set {
        myInt = value;
    }
}

  value等于类型与属性相同的一个值,所以如果属性和字段使用相同的类型,就不必担心数据类型转换了。

  这个简单的属性只能直接访问 myInt 字段。在对操作进行更多的控制时,属性的真正作用才能发挥出来。例如,使用下面的代码实现set块:

set {
    if( value >= 0 && value <= 10 ) {
        myInt = value;
    }
}

  只有赋给属性的值在 0~10 之间,才会改 myInt。此时,要做一个重要的设计选择:如果使用了无效值,该怎么办?有4种选择:

?  什么也不做(如上述代码所示)。
?  给字段赋默认值。
?  继续执行,就好像没有发生错误一样,但记录下该事件,以备将来分析。
?  抛出异常。

  一般情况下,后两个选择效果较好,选择哪个选项取决于如何使用类,以及给类的用户授予多少控制权。抛出异常给用户提供的控制权相当大,可以让他们知道发生了什么情况,并作出适当的响应。为此可以使用System名称空间中的标准异常,例如:

set {
    if( value >= 0 && value <= 10 ) {
        myInt = value;
    } else {
        throw( new ArgumentOutOfRangeException( "MyIntProp", value
            , "MyIntProp must be assigned a value between 0 and 10.") );
    }
}

  这可以在使用属性的代码中通过try ... catch ... finally逻辑来处理,详见第7章。

  记录数据,例如,记最到文本文件中,对产品代码会比较有效,因为产品代码不应发生错误。它们允许开发人员检查性能,如有必要,还可以测试现有的代码。

  属性可以使用virtual、override和abstract关键字,就像方法一样,但这几个关键字不能用于字段。最后,如上所述,访问器可以有自己的可访问性,例如:

// Field used by property
private int myInt;

// Property
public int MyIntProp {
    get {
        return myInt;
    }
   // 只有类或派生类中的代码才能使用set访问器。    protected set {
        myInt = value;
    }
}

  访问器可以使用的访问修饰符取决于属性的可访问性,访问器的可访问性不能高于它所属的属性,也就是说,私有属性对它的访问器不能包含任何可访问修饰符,而公共属性可以对其访问器使用所有的可访问修饰符。

  上示例(星月:“大白,出来!”    大白:“双11,不理你!”   星月:== ),步骤如下:

  1. \Chapter10目录中创建一个新控制台应用程序项目Ch10Ex01。  

  2. 使用“添加类”(Add Class)快捷方式添加一个新类MyClass,这将在新文件MyClass.cs中定义这个新类。修改代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Ch10Ex01
{
    public class MyClass
    {
        public readonly string Name;  // Name必须在构造函数中初始化
        private int intVal;

        public int Val
        {
            get
            {
                return intVal;
            }
            set
            {
                if (value >= 0 && value <= 10)
                {
                    intVal = value;
                }
                else
                {
                    throw (new ArgumentOutOfRangeException("Val", value
                        , "Val must be assigned a value between 0 and 10."));
                }
            }
        }

        public override string ToString()
        {
            return "Name: " + Name + "\nVal: " + Val;
        }

        private MyClass()
            : this("Default Name")
        {  // 调用 MyClass( "Default Name" )

        }

        public MyClass(string newName)
        {
            Name = newName;
            intVal = 0;
        }
    }
}

  3. 修改Program.cs中的代码,如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Ch10Ex01
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Creating object myObj ...");
            MyClass myObj = new MyClass("My Object");
            for (int i = -1; i <= 0; ++i)
            {
                try
                {
                    Console.WriteLine("\nAttempting to assign {0} to myObj.Val ... ", i);
                    myObj.Val = i;
                    Console.WriteLine("Value {0} assigned to myObj.Val", myObj.Val);
                }
                catch (Exception e)
                {
                    Console.WriteLine("Exception {0} thrown.", e.GetType().FullName);
                    Console.WriteLine("Message:\n\"{0}\"", e.Message);
                }
            }

            Console.WriteLine("\nOutputting myObj.ToString() ... ");
            Console.WriteLine(myObj.ToString());
            Console.WriteLine("myObj.ToString() Output.");
            Console.ReadKey();
        }
    }
}

  运行结果:

  

示例的说明

  Main()中的代码创建并使用在MyClass.cs中定义的MyClass类的实例。实例化这个类必须使用非默认的构造函数来进行,因为MyClass类的默认构造函数是私有的:

      private MyClass() : this("Default Name")
      {
      } 

  注意,这里用this("Default Name")来保证,如果调用了该构造函数,Name就获取一个值。

  所使用的非默认构造函数把值赋给只读字段 Name(只能在字段声明或在构造函数中给它赋值)和私有字段intVal。

  , Main()试着给myObj(MyClass的实例)的Va l属性赋值。 for循环在两次循环中赋值-1和0,try ... catch结构用于检查抛出的异常。 把-1赋给属性时, 会抛出System.ArgumentOutOfRangeException类型的异常,catch块中的代码会把该异常的信息输出到控制台窗口中。在下一个循环中,值0成功地赋给Va l属性,通过这个属性再把值赋给私有字段intVal。

  最后,使用重写的ToString()方法输出一个格式化的字符串,来表示对象的内容:

      public override string ToString()
      {
          return "Name: " + Name + "\nVal: " + Val;
      }

  必须使用 override 关键字来声明这个方法,因为它重写了基类 System.Object 的虚拟方法ToString()。此处的代码直接使用属性Va l,而不是私有字段intVal,没有理由不以这种方式使用类中的属性,但这可能会对性能产生比较轻微的影响(对性能的影响非常小,我们不可能觉察到)。当然,使用属性也可以在属性中进行固有的有效性验证,这对类中的代码也是有好处的。

  

时间: 2024-12-26 19:59:04

(原创)c#学习笔记10--定义类成员01--成员定义03--定义属性的相关文章

(原创)c#学习笔记10--定义类成员05--部分方法定义

10.5  部分方法定义 部分类也可以定义部分方法.部分方法在部分类中定义,但没有方法体,在另一个部分类中包含实现代码.在这两个部分类中,都要使用partial关键字. public partial class MyClass { partial void MyPartialMethod(); } public partial class MyClass { partial void MyPartialMethod() { // Method implementation } } 部分方法也可以

(原创)c#学习笔记09--定义类01--c#中的类定义

第九章 定义类 本章内容: ●  如何在C#中定义类和接口 ●  如何使用控制可访问性和继承的关键字 ●  System.Object类及其在类定义中的作用 ●  如何使用VS和VCE提供的一些帮助工具 ●  如何定义类库 ●  接口和抽象类的异同 ●  结构类型的更多内容 ●  复制对象的一些重要信息 9.1  C#中的类定义 C#使用class关键字来定义类: class MyClass { // Class members. } 默认情况下,类声明为内部的,即只有当前项目中的代码才能访问它

(原创)c#学习笔记09--定义类05--类库项目

9.5  类库项目 除了在项目中把类放在不同的文件中之外,还可以把它们放在完全不同的项目中.如果一个项目什么都不包含,只包含类(以及其他相关的类型定义,但没有入口点),该项目就称为类库. 类库项目编译为.dll 程序集,在其他项目中添加对类库项目的引用,就可以访问它的内容(这可以是同一个解决方案的一部分,但这不是必须的).这将扩展对象提供的封装性,因为类库可以进行修改和更新,而不会影响使用它们的其他项目.这意味着,您可以方便地升级类提供的服务(这会影响多个用户应用程序). 下面看一个类库项目的示

(原创)c#学习笔记09--定义类06--接口和抽象类

9.6  接口和抽象类 抽象类和接口都包含可以由派生类继承的成员.接口和抽象类都不能直接实例化,但可以声明这些类型的变量.如果这样做,就可以使用多态性把继承这两种类型的对象指定给它们的变量.接着通过这些变量来使用这些类型的成员,但不能直接访问派生对象的其他成员. 下面看看它们的区别.派生类只能继承一个基类,即只能直接继承一个抽象类(但可以用一个继承链包含多个抽象类).相反,类可以使用任意多个接口.但这不会产生太大的区别——这两种情况取得的效果是类似的.只是采用接口的方式略有不同. 抽象类可以拥有

(原创)c#学习笔记10--定义类成员01--成员定义05--重构成员

10.1.5  重构成员 在添加属性时有一项很方便的技术,可以从字段中生成属性在添加属性时有一项很方便的技术,可以从字段中生成属,下面是一个重构(refactoring)的示例,“重构”表示使用工具修改代码,而不是手工修改.为此,只需右击类图中的某个成员,或者在代码视图中右击某个成员即可. 例如,如果MyClass类包含如下字段: public string myString; 右击该字段,选择“重构 ? 封装字段”(Refactor ?Encapsulate Field),就会打开如图10-7

(原创)c#学习笔记10--定义类成员06--示例应用程序02--编写类库

10.6.2  编写类库 类和枚举都包含在一个类库项目 Ch10CardLib 中.这个项目将包含 4 个.cs 文件,Card.cs 包含Card类的定义,Deck.cs包含Deck类的定义,Suit.cs和Rank.cs文件包含枚举. 1. \Chapter10目录中创建一个新类库项目Ch10CardLib.从项目中删除Class1.cs. 2. 添加:Suit.cs和Rank.cs文件,如下所示修改: using System; using System.Collections.Gener

(原创)c#学习笔记10--定义类成员02--类成员的其他议题02--调用重写或隐藏的基类方法

10.2.2  调用重写或隐藏的基类方法 无论是重写成员还是隐藏成员,都可以在派生类的内部访问基类成员.这在许多情况下都是很有用的,例如: 要对派生类的用户隐藏继承的公共成员,但仍能在类中访问其功能. 要给继承的虚拟成员添加实现代码,而不是简单地用重写的新执行代码替换它. 为此,可以使用 base 关键字,它表示包含在派生类中的基类的实现代码(在控制构造函数时,其用法是类似的,如第9所述),例如: public class MyBaseClass { public virtual void Do

(原创)c#学习笔记10--定义类成员06--示例应用程序03--类库的客户应用程序

10.6.3  类库的客户应用程序 新项目命名为Ch10CardClient.添加一个对类库项目Ch10CardLib的引用.代码如下: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Ch10CardLib; namespace Ch01CardClient { class Program { sta

(原创)c#学习笔记09--定义类07--结构类型

9.7  结构类型 第8章提到过结构和类非常相似,但结构是值类型,而类是引用类型.这意味着什么?最简明的方式是用一个示例来说明.代码如下: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Ch09Ex03 { class MyClass { public int val; } struct my