面向对象设计和编程的重要原则之一就是数据封装,意味着类型的字段永远不应该公开,否则很容易因为不恰当使用字段而破坏对象的状态。
无参属性
对于类型中数据字段的封装,有以下3点好处:
- 可能希望访问字段来执行一些“副作用”,缓存某些值或者推迟创建一些内部对象
- 可能希望以线程安全的方式访问字段
- 字段可能是一个逻辑字段,它的值不由内存中的字节表示,而是通过某个算法来计算获得
public sealed class Employee
{
private int m_age1;
public int get_Age()
{
return m_age1;
}
public void set_Age(int value)
{
m_age1 = value;
}
// 等效于;编译器在指定的属性名之前自动附加get或者set前缀
private int m_age2;
public int M_age2 { get => m_age2; set => m_age2 = value; }
// 等效于;C#提供更简洁的语法,称为自动实现的属性(Automatically Implemented Property,AIP)
public int M_age3 { get; set; }
}
使用AIP,意味着你已经创建了一个属性。访问该属性的任何代码实际都会调用get和set方法。如果以后你决定自己实现get或set方法,而不是接受编译器的默认实现,访问属性的任何代码都不必重新编译。
但AIP有个问题是,运行时序列化引擎将字段名持久存储在序列化的流中。AIP的支持字段名称由编译器决定,每次重新编译代码都可能更改这个名称。因此,任何类型只要含有一个AIP,就没办法对该类型的实例进行反实例化。在任何想要序列化或反序列化的类型中,都不要使用AIP功能。
除此之外,如果使用AIP,属性必然是可读和可写的。
对象和集合初始化器
经常要构建一个对象并设置对象的一些公共属性(或字段)。为了简化这个常见的编程模式,C#语言支持了一种特殊的对象初始化语法。
public sealed class Employee
{
public string name;
public int age;
}
public class Program
{
static void Main(string[] args)
{
Employee e1 = new Employee() { name = "Jeff", age = 45 };
// 等效于
Employee e2 = new Employee();
e2.name = "Jeff";
e2.age = 45;
}
}
如果属性的类型实现了IEnumerable或IEnumerable<T>接口,属性就被认为是集合,而集合的初始化是一种相加(additive)操作,而非替换(replacement)操作。例如:
public sealed class ClassRoom
{
public List<string> students = new List<string>();
}
public class Program
{
static void Main(string[] args)
{
ClassRoom c1 = new ClassRoom() { students = new List<string>() { "A", "B", "C" } };
// 等效于
ClassRoom c2 = new ClassRoom();
c2.students.Add("A");
c2.students.Add("B");
c2.students.Add("C");
}
}
匿名类型
我们可以在不定义类型的情况,去创建一个自定义的类型实例,例如:
public class Program
{
static void Main(string[] args)
{
// 定义类型,构造实例,并初始化属性
var o1 = new { name = "Jeff", age = 45 };
Console.WriteLine($"name:{o1.name},age:{o1.age}");
}
}
第一行代码创建了匿名类型,没有在new关键字后指定类型名称,所以编译器会自动创建类型名称,而且我们无法知道这个名称是什么(正是匿名的含义)。
编译器会推断每个表达式的类型,创建推断类型的私有字段,为每个字段创建公共只读属性,并创建一个构造器来接受所有这些表达式。除此之外,编译器还会重写Object的Equals,GetHashCode和ToString方法,并生成所有这些方法的代码,类似这种:
internal sealed class f_AnonymousType0: Object
{
private readonly string name;
private readonly int age;
public string Name => name;
public int Age => age;
public f_AnonymousType0(string name, int age)
{
this.name = name;
this.age = age;
}
public override bool Equals(object obj)
{
....
}
public override int GetHashCode()
{
....
}
public override string ToString()
{
...
}
}
编译器会生成Equals和GetHashCode方法,因此匿名方法的实例能放到哈希表集合中。属性是只读的,而非可读可写,目的是防止对象的哈希码发送改变。
编译器在定义匿名类型时是非常“善解人意”的。如果它看到你在源代码中定义了多个匿名类型,而且这些类型具有相同的结构,那么它只会创建一个匿名类型定义,但创建该类型的多个实例。
由于类型相等,所以我们可以检查2个对象是否包含相等的值,并将一个对象的引用赋给另一个对象的变量,比如:
public class Program
{
static void Main(string[] args)
{
var o1 = new { name = "Jeff", age = 45 };
var o2 = new { name = "Petter", age = 25 };
Console.WriteLine($"isEqual:{o1.Equals(o2)}");
o2 = o1;
}
}
也可以创建一个隐式类型的数据,在其中包含一组匿名类型的对象,比如:
public class Program
{
static void Main(string[] args)
{
var perpon = new[]
{
new {name = "Jeff", age = 45},
new {name = "Petter", age = 25}
};
}
}
元组
匿名类型的实例不能泄露到方法外部。方法原型不能接受匿名类型的参数,因为无法指定匿名类型。这个时候,就可以利用到元组了。在System命名空间,Microsoft定义了几个泛型Tuple类型,它们全部从Object派生,区别只在于元数(泛型参数的个数)。最简单的元组如下:
public class Tuple<T1>
{
private T1 m_Item1;
public Tuple(T1 item1) { m_Item1 = item1; }
public T1 Item1 => m_Item1;
}
有参属性
无参属性get,set访问权不接受参数,有参属性则反过来,它的get或set访问器可以接受多个参数,有参属性亦称索引器。C#使用数组风格的语法来公开有参属性(索引器)。下面是一个示例:
public class WeekDays
{
private string[] days = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };
public string this[int index]
{
get
{
if (index < 0 || index >= days.Length)
{
throw new IndexOutOfRangeException("Index out of range.");
}
return days[index];
}
set
{
if (index < 0 || index >= days.Length)
{
throw new IndexOutOfRangeException("Index out of range.");
}
days[index] = value;
}
}
}
所有索引器至少要有一个参数,或者更多。这些参数(和返回类型)可以是除了void之外的任意类型。C#允许一个类型定义多个所引起,只要索引器的参数集不同。
调用属性访问器时的性能
对于简单的get和set访问器方法,JIT编译器会将代码内联。这样一来,使用属性就没有性能上的损失。内联是指将方法的代码直接编译到调用它的方法中。这就避免了在运行时发出调用所产生的开销,代价是编译好的方法变得更大。由于属性访问器方法包含的代码一般很少,所以对内联会使生成的本机代码变得更小,而且执行更快。
标签:无参,via,name,age,类型,new,public,属性 From: https://www.cnblogs.com/chenxiayun/p/18390397