属性作为虚字段
属性还能这么玩儿:属性可以不支持字段,也就是当做虚字段玩儿,也就是根本没往内存中写入Name这个字段值!属性被当成方法实现了,其实不太建议这么玩儿。
namespace AddisonWesley.Michaelis.EssentialCSharp.Chapter06.Listing06_22 { using System; using System.IO; public class Program { public static void Main() { Employee employee1 = new Employee(); employee1.Name = "Inigo Montoya"; System.Console.WriteLine(employee1.Name); // ... } } class Employee { // FirstName property public string FirstName { get { return _FirstName; } set { _FirstName = value; } } private string _FirstName; // LastName property public string LastName { get => _LastName; set => _LastName = value; } private string _LastName; // ... // Name property public string Name { get { return $"{ FirstName } { LastName }"; } set { // Split the assigned value into // first and last names string[] names; names = value.Split(new char[] { ' ' }); if(names.Length == 2) { FirstName = names[0]; LastName = names[1]; } else { // Throw an exception if the full // name was not assigned throw new System.ArgumentException( $"Assigned value '{ value }' is invalid", "value"); } } } public string Initials => $"{ FirstName[0] } { LastName[0] }"; // Title property public string Title { get; set; } // Manager property public Employee Manager { get; set; } } }
由此引发的思考就是:为啥不能用ref和out来修饰属性呢,你想啊,如果属性被当成虚字段使,内存中根本没地址,怎么传递地址啊,ref是需要初始化的,你这就相当于没有初始化字段。
属性的内部构造
事实上,通过观察IL代码,属性就是一种方法实现,我可以理解其为一种语法糖:
构造器和终结器
其实构造函数都比较熟悉了,就聊一点儿需要注意的地方:
- 构造函数赋值优先于字段声明赋值,所以构造函数的赋值会覆盖字段声明赋值
- C#编译器默认支持无参构造函数,一旦显式添加,即使是手动添加无参的,编译器也不会再提供默认的
构造函数可以重载,一个使用规范是:要优先调用可选参数的方法而不是重载。
对象初始化器
对象初始化器,其实就是一种语法糖,需要注意的是,初始化时会按顺序赋值:
public static void Main() { Employee employee = new Employee("Inigo", "Montoya") { Title = "Computer Nerd", Salary = "Not enough" }; }
同样集合初始化器也一个道理,注意,会按顺序添加哦!
构造函数链
构造函数可以互相调用,避免代码冗余:
public class Employee { public Employee(string firstName, string lastName) { FirstName = firstName; LastName = lastName; } public Employee( int id, string firstName, string lastName) : this(firstName, lastName) //实际情况是参数少的调用参数多的 { Id = id; } public Employee(int id) { Id = id; } public int Id { get; private set; } public string FirstName { get; set; } public string LastName { get; set; } public string Salary { get; set; } = "Not Enough"; // ... }
通常做法是这样的:
public Employee(string firstName, string lastName):this(firstName, lastName,"add") { FirstName = firstName; LastName = lastName; } public Employee(string firstName, string lastName,string test=null) { FirstName = firstName; LastName = lastName; }
注意:调用方的参数一定要包含被调用方的全部参数!,例如这样调用:
集中初始化和解构函数
集中初始化没什么好说的,就是把所有属性参数什么的在一个函数里一次性初始化,通常是构造函数。而解构函数就是通过Deconstruct方法:
public void Deconstruct( out int id, out string firstName, out string lastName, out string salary) { (id, firstName, lastName, salary) = (Id, FirstName, LastName, Salary); }
调用的时候:
employee.Deconstruct(out id, out firstName,out lastName, out salary);
C#7.0后支持直接将对象解构为元组,无需方法调用:
(_,firstName,lastName,salary) = employee
静态
静态字段、方法已经在前文说明了,这里简单说一下其特性:
- 静态字段的作用域为其所属的类。
- 无论是方法(静态、实例)还是字段(静态、实例),都不能同名,方法重载除外,是说这四种都不能重名!
- 和普通构造函数一样,静态构造函数赋值优先于静态字段声明!
- 静态类不包含任何实例成员:包括字段、方法等。
有个地方需要注意:最好在声明时进行静态初始化(而不要使用静态构造函数):静态构造函数在首次访问类的任何成员之前执行,无论该成员是静态字段,是其他静态成员,还是实例构造函数。为支持这个设计,编译器添加代码来检查类型的所有静态成员和构造函数,确保首先运行静态构造函数。如果没有静态构造函数,编译器会将所有静态成员初始化为它们的默认值,而且不会添加对静态构造函数的检查。结果是静态字段会在访问前得到初始化,但不一定在调用静态方法或任何实例构造函数之前。有时对静态成员进行初始化的代价比较高,而且访问前确实没必要初始化,所以这个设计能带来一定的性能提升。有鉴于此,请考虑要么以内联方式初始化静态字段(而不要使用静态构造函数),要么在声明时初始化。
局部变量、实例字段和静态字段
局部变量如果使用前没有赋值会报错,静态字段则会使用默认值,实例字段如果在对象生成后也会使用默认值:
静态方法和实例方法
实例方法可以直接调用静态方法,静态方法不能通过this调用实例方法,只能实例化一个对象,再调用实例方法:
public static void fecond() { Class1 ct = new Class1(); ct.second(); }
const和readonly
const就是常量的意思,默认为静态字段,而且只限于字面值类型(int,long,string)。此时再声明static反而会报错。而readly也可以用来声明字段,值得注意的是,只能声明字段,而不能声明局部变量!readonly表明字段值只能从构造函数中修改或者初始化器指定,但其实只读自动属性一用,readonly就没啥用了。
扩展方法和特殊类
包括:扩展方法、嵌套类、分部类及分部方法:其实这一部分,我觉得可以用两个字概述:解耦,做的事情无非就是将耦合度降低。
扩展方法
扩展方法在上文提到过,感觉实则没有任何用处,需要注意的是,即使不在一个程序集的类也可以添加。
public class Program { public static void Main() { DirectoryInfo directory = new DirectoryInfo(".\\Source"); directory.CopyTo(".\\Target", SearchOption.TopDirectoryOnly, "*"); } } public static class DirectoryInfoExtension { public static void CopyTo( this DirectoryInfo sourceDirectory, string target, SearchOption option, string searchPattern) //扩展方法的声明,需要this关键字。 { } }
嵌套类
一般类的 访问修饰符可以定义为默认的internal 或者public,而内嵌类就有比较多的选择,可以是为protected、internal、public以及默认的private。嵌套类可以访问外部类的方法、属性、字段而不管访问修饰符的限制
namespace AddisonWesley.Michaelis.EssentialCSharp.Chapter06.Listing06_46 { using System; public class Program { // Define a nested class for processing the command line private class CommandLine { public CommandLine(string[] arguments) { // } public string Action; public string Id; public string FirstName; public string LastName; } public static void Main(string[] args) { CommandLine commandLine = new CommandLine(args); } } }
分部类和分部方法
无非就是把类和方法分别放到不同的文件中去,
namespace AddisonWesley.Michaelis.EssentialCSharp { // We dont fully implement our switch block here #pragma warning disable CS1522 using System; // File: Program.cs partial class Program { static void Main(string[] args) { CommandLine commandLine = new CommandLine(args); switch(commandLine.Action) { // ... } } } // File: Program+CommandLine.cs partial class Program { // Define a nested class for processing the command line private class CommandLine { public CommandLine(string[] args) { //not implemented } // ... public int Action { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } } } #pragma warning restore CS1522 }
使用关键字partial ,这里就不深入探讨了。
本篇博客从学习到完成历时两天,感觉学到了特别多的干货和东西,最重要的就是从设计的角度去认知为什么要这么设计类,希望能帮助到大家。