属性,属性

无参属性


  属性是类或对象中的一种智能字段形式。从对象外部,它们看起来像对象中的字段。 如下定义了一个属性Name,属性包含get和set访问器的声明。

public class Person
{
    private string name;
    public string Name
    {
        get { return name; }
        set { name = value; }
    }
}

为什么不直接使用类型的成员字段?

面向对象设计和编程的重要原则之一就是数据封装,类型的字段不应该公开,否则很容易因为不恰当使用字段而破坏对
象的状态。所以应提供公有的方法访问私有的字段,封装了字段访问的方法通常叫做访问器方法,访问器方法可以
对数据的合理性进行检查,确保对象的状态永远不被破坏。

下面,通过查看ildasm工具,了解编译器在定义属性时做了哪些工作。

如上图所示,编译器生成了get和set访问器方法,并且在属性名之前自动附加了get_和set_前缀来命名方法。除此之外,还生成了一个属性的定义项(IL代码如下),在这个记录项中包含了一些flag、属性的类型以及对get和set访问器方法的引用,这些元数据信息可供编译器和其他工具使用。

.property instance string Name()
{
    .get instance string Property.Person::get_Name()
    .set instance void Property.Person::set_Name(string)
} // end of property Person::Name

自动属性

  如果只是为了封装一个支持字段而创建属性,C#提供了自动实现的属性(Automatically Implemented Property,AIP),语法如下,也可以键入prop+两个tab键快速生成自动属性

    public string Name { get; set; }

编译器会自动声明一个string类型的私有字段,并实现get_Name和set_Name方法。注意:使用AIP,属性是可读可写的。另外,如果需要显示实现get或set访问器方法,就必须同时显示实现。在C#6和更高版本,可以像字段一样初始化自动实现属性。

    public string FirstName { get; set; } = "Mike";

对象和集合初始化器

  一般要构造一个新对象并设置对象的一些公共属性的代码如下:

class Program
{
    static void Main(string[] args)
    {
        Person person = new Person();
        person.Name = "Mike";
        person.Age = 24;
    }
}

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

为了简化这个常见的编程模型,C#提供了一种简单的初始化语法:

Person person = new Person { Name = "Mike", Age = 24 };

由于调用的是Person类的无参构造函数,这里省略了小括号,并在语句的结尾处的大括号内设置了person对象的Name和Age属性。

如果属性的类型实现了IEnumerable或IEnumerable<T>接口,属性就被认作是集合,为Person类添加Hobbies属性。

private List<string> hobbies = new List<string>();
public List<string> Hobbies
{
    get { return hobbies; }
    set { hobbies = value; }
}

构造Person对象,设置Name、Age和Hobbies属性:

Person person = new Person { Name = "Mike", Age = 24, Hobbies = { "reading", "swimming" } };

编译上述代码时,编译器发现Hobbies属性的类型是List<string>,该类型实现了IEnumerable<string>接口,然后生成代码来调用集合的Add方法。因此,上述代码会转换成:

Person person = new Person();
person.Name = "Mike";
person.Age = 24;
person.Hobbies.Add("reading");
person.Hobbies.Add("swimming");

匿名类型

  使用C#的匿名类型,可以用简洁的语法来声明类型,如上节中定义的Person类型,可以使用如下代码:

 var person = new { Name = "Mike", Age = 24, Hobbies = new List<string>() { "reading", "swimming" } };

下面通过ildasm工具查看这段程序的元数据信息,看看编译器都做了什么。

编译器会推断出每个表达式的类型,创建私有字段,为每个字段创建公共只读属性,并创建一个构造函数来接受这些表达式,在构造函数代码中(IL代码如下),会用传入表达式的求值结果来初始化私有的只读字段。除此之外,编译器还会重写Object类的Equals、GetHashCode和ToString方法。

.method public hidebysig specialname rtspecialname 
        instance void  .ctor(!'<Name>j__TPar' Name,
                            !'<Age>j__TPar' Age,
                            !'<Hobbies>j__TPar' Hobbies) cil managed
{
.custom instance void [mscorlib]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = ( 01 00 00 00 ) 
// Code size       28 (0x1c)
.maxstack  8
IL_0000:  ldarg.0
IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
IL_0006:  ldarg.0
IL_0007:  ldarg.1
IL_0008:  stfld      !0 class '<>f__AnonymousType0`3'<!'<Name>j__TPar',!'<Age>j__TPar',!'<Hobbies>j__TPar'>::'<Name>i__Field'
IL_000d:  ldarg.0
IL_000e:  ldarg.2
IL_000f:  stfld      !1 class '<>f__AnonymousType0`3'<!'<Name>j__TPar',!'<Age>j__TPar',!'<Hobbies>j__TPar'>::'<Age>i__Field'
IL_0014:  ldarg.0
IL_0015:  ldarg.3
IL_0016:  stfld      !2 class '<>f__AnonymousType0`3'<!'<Name>j__TPar',!'<Age>j__TPar',!'<Hobbies>j__TPar'>::'<Hobbies>i__Field'
IL_001b:  ret
} // end of method '<>f__AnonymousType0`3'::.ctor

到这里我们已经大概了解了匿名类型的使用方法,下面请看这段代码:

var mike = new { Name = "Mike", Age = 24, Hobbies = new List<string>() { "reading", "swimming" } };
var jack = new { Name = "Jack", Age = 31, Hobbies = new List<string>() { "coding", "playing basketball" } };

那么问题来了,这段代码究竟会生成几个类呢?查看元数据信息,答案是一个。看来编译器还是很智能的,对于具有相同结构的匿名类型,只会创建一个类型的定义。这时我们再来做个试验,把Name和Age的属性调换一下位置。

var mike = new { Name = "Mike", Age = 24, Hobbies = new List<string>() { "reading", "swimming" } };
var jack = new { Age = 31, Name = "Jack", Hobbies = new List<string>() { "coding", "playing basketball" } };

编译后再次查看元数据,发现这时生成了两个类型的定义。

结论:当定义多个匿名类型时,如果每个属性都有相同的类型和名称,并且这些属性指定的顺序完全相同,那么它只会创建一个匿名类型的定义。

由于编译器还有这种操作,为我们使用匿名类型定义一组对象构成集合提供了可能性,没错,我说的就是配合LINQ的select操作符构建对象的集合,示例如下:

class Employee
{
    public string EmployeeId { get; private set; }
    public string EmployeeName { get; private set; }
    public int DepartmentId { get; private set; }
    public int Age { get; private set; }
    public int Salary { get; private set; }

    public Employee(string employeeId, string employeeName, int departmentId, int age, int salary)
    {
        this.EmployeeId = employeeId;
        this.EmployeeName = employeeName;
        this.DepartmentId = departmentId;
        this.Age = age;
        this.Salary = salary;
    }
}

class Department
{
    public int DepartmentId { get; private set; }
    public string DepartmentName { get; private set; }
    public Department(int departmentId, string departmentName)
    {
        this.DepartmentId = departmentId;
        this.DepartmentName = departmentName;
    }
}

static void Main(string[] args)
{
    List<Employee> employees = new List<Employee>
    {
        new Employee("001","Mike",2,24,30000),
        new Employee("002","Jack",1,34,40998),
        new Employee("003","Altony",2,26,58888),
        new Employee("004","Carl",3,32,350000),
        new Employee("005","Duncan",1,34,100000)
    };

    List<Department> departments = new List<Department>
    {
        new Department(1,"HR"),
        new Department(2,"IT"),
        new Department(3,"FT"),
    };

    var query = from q in employees
                join t in departments on q.DepartmentId equals t.DepartmentId
                where q.Age > 30
                select new
                {
                    Name = q.EmployeeName, Age = q.Age, DepartmentName = t.DepartmentName
                };  //匿名类型的对象构成的集合

    foreach (var employee in query)
    {
        Console.WriteLine($"EmployeeName= {employee.Name},Age={employee.Age},DepartmentName={employee.DepartmentName}");
    }

    Console.ReadLine();
}

有参属性


  上一节中,属性的get访问器方法不接受参数,因此称为无参属性。除此之外,C#还支持有参属性(也称为索引器)。

class SampleCollection
{
    private int[] arr = new int[10];
    public int this[int i]
    {
        get
        {
            return arr[i] * 100 + 300;
        }
        set
        {
            arr[i] = value;
        }
    }
}

 static void Main(string[] args)
 {
    SampleCollection sample = new SampleCollection();
    sample[0] = 100;
    Console.WriteLine(sample[0]);   //Output:10300
    Console.ReadLine();
}

根据以上示例,能够看出:

  1. 和无参属性类似,索引器也需要定义get,set访问器,并且在set访问器中可以使用value关键字。
  2. 索引器的get访问器需要接受参数。
  3. 使用this关键字定义索引器。

老样子,查看程序集元数据信息。

编译器生成的get/set访问器方法的默认名称为get/set_Item,C#允许向索引器应用定制特性来重命名这些方法。

class SampleCollection
{
    private int[] arr = new int[10];
    [System.Runtime.CompilerServices.IndexerName("Answer")]
    public int this[int i]
    {
        get
        {
            return arr[i] * 100 + 300;
        }
        set
        {
            arr[i] = value;
        }
    }
}

posted @ 2017-09-23 15:29  Answer.Geng  阅读(612)  评论(1编辑  收藏  举报