【C#本质论 六】类-从设计的角度去认知(封装)(下)

简介: 【C#本质论 六】类-从设计的角度去认知(封装)(下)

属性作为虚字段

属性还能这么玩儿:属性可以不支持字段,也就是当做虚字段玩儿,也就是根本没往内存中写入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代码,属性就是一种方法实现,我可以理解其为一种语法糖:

构造器和终结器

其实构造函数都比较熟悉了,就聊一点儿需要注意的地方:

  1. 构造函数赋值优先于字段声明赋值,所以构造函数的赋值会覆盖字段声明赋值
  2. 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 ,这里就不深入探讨了。

本篇博客从学习到完成历时两天,感觉学到了特别多的干货和东西,最重要的就是从设计的角度去认知为什么要这么设计类,希望能帮助到大家。

相关文章
|
26天前
|
开发框架 .NET C#
C#|.net core 基础 - 删除字符串最后一个字符的七大类N种实现方式
【10月更文挑战第9天】在 C#/.NET Core 中,有多种方法可以删除字符串的最后一个字符,包括使用 `Substring` 方法、`Remove` 方法、`ToCharArray` 与 `Array.Copy`、`StringBuilder`、正则表达式、循环遍历字符数组以及使用 LINQ 的 `SkipLast` 方法。
|
2月前
|
存储 C# 索引
C# 一分钟浅谈:数组与集合类的基本操作
【9月更文挑战第1天】本文详细介绍了C#中数组和集合类的基本操作,包括创建、访问、遍历及常见问题的解决方法。数组适用于固定长度的数据存储,而集合类如`List<T>`则提供了动态扩展的能力。文章通过示例代码展示了如何处理索引越界、数组长度不可变及集合容量不足等问题,并提供了解决方案。掌握这些基础知识可使程序更加高效和清晰。
73 2
|
2月前
|
安全 C#
C# 面向对象编程的三大支柱:封装、继承与多态
【9月更文挑战第17天】在C#中,面向对象编程的三大支柱——封装、继承与多态,对于编写安全、可维护、可复用的代码至关重要。封装通过访问修饰符和属性保护数据;继承允许子类继承父类的属性和方法,实现代码复用和多态;多态则提高了代码的灵活性和通用性。掌握这三大概念能显著提升C#编程能力,优化开发效率和代码质量。
|
24天前
|
Java 程序员 C#
【类的应用】C#应用之派生类构造方法给基类构造方法传参赋值
【类的应用】C#应用之派生类构造方法给基类构造方法传参赋值
9 0
|
2月前
|
C# 数据安全/隐私保护
C# 一分钟浅谈:类与对象的概念理解
【9月更文挑战第2天】本文从零开始详细介绍了C#中的类与对象概念。类作为一种自定义数据类型,定义了对象的属性和方法;对象则是类的实例,拥有独立的状态。通过具体代码示例,如定义 `Person` 类及其实例化过程,帮助读者更好地理解和应用这两个核心概念。此外,还总结了常见的问题及解决方法,为编写高质量的面向对象程序奠定基础。
24 2
|
3月前
|
C#
C#中的类和继承
C#中的类和继承
40 6
|
3月前
|
Java C# 索引
C# 面向对象编程(一)——类
C# 面向对象编程(一)——类
33 0
|
3月前
|
开发框架 .NET 编译器
C# 中的记录(record)类型和类(class)类型对比总结
C# 中的记录(record)类型和类(class)类型对比总结
|
5月前
|
开发框架 .NET 编译器
程序与技术分享:C#基础知识梳理系列三:C#类成员:常量、字段、属性
程序与技术分享:C#基础知识梳理系列三:C#类成员:常量、字段、属性
36 2
|
5月前
|
C#
C# 版本的 计时器类 精确到微秒 秒后保留一位小数 支持年月日时分秒带单位的输出
这篇2010年的文章是从别处搬运过来的,主要包含一个C#类`TimeCount`,该类有多个方法用于处理时间相关的计算。例如,`GetMaxYearCount`计算以毫秒为单位的最大年数,`GetCurrentTimeByMiliSec`将当前时间转换为毫秒,还有`SecondsToYYMMDDhhmmss`将秒数转换为年月日时分秒的字符串。此外,类中还包括一些辅助方法,如处理小数点后保留一位数字的`RemainOneFigureAfterDot`。