重庆熊猫 Loading

C#教程 - 类类型(Class Type)

更新记录
转载请注明出处:https://www.cnblogs.com/cqpanda/p/16678364.html
2022年9月14日 发布。
2022年9月10日 从笔记迁移到博客。

C# 中类的类型

  1. Abstract class
  2. Concrete class
  3. Sealed class
  4. Partial Class
  5. Static class

定义类(Class Type)

定义普通类(Concrete class)

[Attributes]
[modifiers] class [ClassNane]<Generic type parameters> : [interfaces] [BaseClass]
{

  [Class members]
}

说明:

  • [Attributes]表示特性
  • [modifiers]表示类的修饰符,可以是:public、internal、abstract、sealed、static、unsafe、partial
  • <Generic type parameters>表示泛型参数
  • [interfaces]表示接口
  • [BaseClass]表示基类
  • [Class members]表示类成员,可以是:methods、properties、indexers、events、fields、constructors、overloaded operators、nested types、and a finalizer

定义分部类(Partial Class)

提示:分部类可以声明在 不同文件 或 同一个文件中
注意:每个 分部类 都必须使用 partial 修饰
注意:
分部类最终会被编译到一个类中,编译到一个程序集中
编译器不保证部分类型声明之间的字段初始化顺序
分部类使用场景:
​ 类中的部分代码是自动生成的,部分代码是人员编写的
实例:

partial class Panda
{

}

实例:自动生成代码

// PaymentFormGen.cs - autogenerated
partial class PaymentForm { ... }
// PaymentForm.cs - hand-authored
partial class PaymentForm { ... }

定义静态类(Static class)

静态类中的所有成员都是静态的,静态类不可以进行实例化
使用static标注类即可
静态类可以有一个静态构造函数,但不可以有实例构造函数
静态类是隐式密封的,不能继承静态类
提示:作为工具使用的类,可以尝试定义成静态类型

static class PandaTest
{
  public static int i = 123;
  public static string name = "test";
}

定义密封类(Sealed Classes)

将类标注为密封后,其他类无法继承该类,无法从写该类的成员
注意:封闭类的成员默认也是seal的,不可以进行override
使用关键字sealed修饰类即可

sealed class PandaTest
{

}

定义嵌套类(Nested Types)

All types (classes, structs, interfaces, delegates, and enums) can be nested within either a class or a struct

class Outer
{
    public class Inner
    {
    }
}

嵌套类型的特点:

  • 可以访问上级类型可以访问的任何类型
  • 可以访问上级类型的私有成员
  • 可以声明成private、public、protected、internal
  • 默认声明为private
  • 从外部访问被嵌套的类型,要用:OtherClass.InnterClass的方式

嵌套类型使用场景:

  • 嵌套enum等类型,给自己的成员使用
  • 编译器内部将匿名方法转为嵌套类型
  • 嵌套Enumerator等类型,给自己的成员使用
    注意:
  • 这里的嵌套只是声明嵌套而已,使用时可以在外部使用
  • 在嵌套类型外部访问被封闭类型需要使用嵌套语法:外部类型.内部类型
  • 内部类可以使用public、private、protected、internal、protected internal修饰
  • 内部类可以使用public、private、internal修饰
  • 内部类型默认修饰符为private
  • 嵌套内部类可以访问外部类的所有成员
  • 内部类型可以访问封闭类型的私有成员
  • 所有的类型都可以被嵌套,但只有类和结构可以包含嵌套类型

嵌套类与外部类的访问性:
image

实例:

using System;
namespace ConsoleApp1
{
    public class PandaOutsideClass
    {
        public class PandaInsideClass
        {
            public string Slogan { get; set; } = "Panda666";
            public void DoSomething()
            {
                Console.WriteLine("PandaInsideClass Do");
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
          //实例化
          PandaOutsideClass.PandaInsideClass obj = new PandaOutsideClass.PandaInsideClass();
          obj.DoSomething();
          Console.WriteLine(obj.Slogan);
          //wait
          Console.ReadKey();
        }
    }
}

实例:嵌套class类型和enum类型

public class TopLevel
{
    public class Nested { } // Nested class
    public enum Color { Red, Blue, Tan } // Nested enum
}

实例:使用嵌套的类型

public class TopLevel
{
    public class Nested { }
}

class Test
{
    TopLevel.Nested n;
}

实例化类

实例化类在内存中示意图

定义了实例变量,在栈上分配了内存,但未在堆中分配内存
image

为实例变量赋值后,在堆上分配空间,并将栈上的引用指向该堆空间
image

基本实例化

class Panda
{
}
Panda obj = new Panda();

对象初始化器(Object Initializers)

在类创建实例的时候,在尾部表达式放置一组成员初始化语句,然后再语句中设置字段和属性值
对象初始化语法:
image

注意:对象初始化的成员必须使用public权限
注意:对象初始化发生再构造函数执行之后
注意:与命名参数进行区分

实例1:

class Panda
{
    static void Main(string[] args)
    {
    ​   Dog someDog = new Dog() { code = 666,name="Tom",price=33.11M }
​       Console.ReadKey();
    }
}

class Dog
{
public int code;
public string name;
public decimal price;
}

实例2:

using System;
namespace Test
{
    /// <summary>
    /// 定义Panda类
    /// </summary>
    class Panda
    {
       public int X { get; set; }
       public int Y { get; set; }
    }

    class Program
    {
       static void Main()
       {
         //实例化并初始化该属性
         Panda a = new Panda() { X = 10, Y = 100 };
       }
    }
}

this关键字(this Reference)

this说明

指向类实例化后对象本身(reference refers to the instance itself)

this只能用在以下地方

构造函数内
类/结构的函数成员内,比如:方法、属性、索引器、事件

实例:this在构造函数内

public Panda(int x, int y)
{
    this._x = x;
    this._y = y;
}

实例:this在方法内

public int GetSum()
{
    return this._x + this._y;
}

实例:this在属性内

public int X 
{
    get
    {
        return this._x;
    }
    set
    {
        this._x = value;
    }
}

实例:this在索引器内

public int this [int index]
{
    get
    {
        if (index == 0)
        {
            return this._x;
        }
        else
        {
            return this._y;
        }
    }
    set
    {
        if (index == 0)
        {
            this._x = value;
        }
        else
        {
            this._y = value;
        }
    }
}

实例:整体实例

using System;

namespace Test
{
    /// <summary>
    /// 定义Panda类
    /// </summary>
    class Panda
    {
        private int _x;
        private int _y;
        public Panda(int x, int y)
        {
            this._x = x;
            this._y = y;
        }

        public int X 
        {
            get
            {
                return this._x;
            }
            set
            {
                this._x = value;
            }
        }

        public int GetSum()
        {
            return this._x + this._y;
        }

        public int this [int index]
        {
            get
            {
                if (index == 0)
                {
                    return this._x; 
                }
                else
                {
                    return this._y;
                }
            }

            set
            {
                if (index == 0)
                {
                    this._x = value;
                }
                else
                {
                    this._y = value;
                }
            }
        }
    }
    class Program
    {
        static void Main()
        {
            //实例化并初始化该属性
            Panda a = new Panda(666,888);
            Console.WriteLine(a.X);
            Console.WriteLine(a.GetSum());
            Console.WriteLine(a[0]);
            Console.WriteLine(a[1]);
        }
    }
}

this作用

智能提示
引用实例自身成员(字段、属性、方法、索引器、构造函数等)
链式调用构造函数

访问修饰符(modifiers)

访问修饰符

分类:

访问修饰符 说明
public Fully accessible. This is the implicit accessibility for members of an enum or interface.
internal Accessible only within the containing assembly or friend assemblies.This is the default accessibility for non-nested types
private Accessible only within the containing type. This is the default accessibility for members of a class or struct.
protected Accessible only within the containing type or subclasses.
protected internal The union of protected and internal accessibility.A member that is protected internal is accessible in two ways
private protected (from C# 7.2)The intersection of protected and internal accessibility.A member that is private protected is accessible only within the containing type,or subclasses that reside in the same assembly(making it less accessible than protected or internal alone).可在 类内部访问 或 继承的类中访问(并集关系)

image

权限对比:
image

public与private示意图:
image

protected internal可见性:
image

internal可见性:
image

类类型支持public和internal修饰符

标记为 internal 的类只能在 程序集 内可见

需要跨程序集访问需要将类声明为public
image

注意:

类类型成员不带修饰符默认是private修饰

​ 类类型不带修饰符默认是internal修饰

​ 抽象成员声明不可以使用virtual修饰

​ interface类型默认的成员修饰符为public

​ enum类型默认的成员修饰符为public

其他修饰符

sealed

​ 说明:表示密封类或密封方法

​ 注意:sealed可以修饰类,也可以修饰方法

abstract

​ 说明:表示抽象类或抽象方法

​ 抽象成员可以是:

​ 方法、属性、索引器、事件

​ 注意:

​ 抽象成员的实现必须使用override修饰

virtual和override

​ 说明:virtual表示虚方法,可以在继承类中重新实现

​ 说明:override表示重写,表示在继承类型中重新实现函数成员

new

​ 说明:表示实例化一个实例 或 表示覆盖子类成员

private methods 和 sealed methods 区别

The private method is not inherited whereas the sealed method is inherited but cannot be overridden in C#. So, a private method cannot be called from sub-classes whereas a sealed method can be called from sub-classes. The same private method can be defined in sub-class and it does not lead to error.

类成员-概述

类成员类型

分为:data member 和 function members
image

数据成员 - 字段(fields)

声明字段

class Panda
{
    private string _name;
}

声明并初始化字段

注意:字段初始化发生在构造函数执行之前,并按字段的声明顺序进行

class Panda
{
    private string _name = "Panda666.com";
}

字段支持的修饰符

静态修饰符(Static modifier) static

访问修饰符(Access modifiers) public internal private protected

继承修饰符(Inheritance modifier) new

不安全代码修饰符(Unsafe code modifier) unsafe

只读修饰符(Read-only modifier) readonly

线程修饰符(Threading modifier) volatile

类常量字段可以使用的修饰符:

访问修饰符(Access modifiers) public internal private protected

继承修饰符(Inheritance modifier) new

静态字段(Static Fields)

class Panda
{
    public static string skill = "C#";
}

静态字段定义时间:

静态字段将在静态构造函数调用之前初始化

如果没有静态构造函数,则字段将在调用之前初始化,按静态字段的定义顺序初始化

实例:静态字段按声明顺序初始化

class Foo
{
  public static int X = Y;    // 0
  public static int Y = 3;    // 3
}

实例:静态字段按声明顺序初始化

using System;
namespace PandaTest
{
    class Foo
    {
        public static Foo Instance = new Foo();
        public static int X = 3;

        Foo() { Console.WriteLine(X); }   // 0
    }

    class Program
    {
        static void Main()
        {
            Foo foo = Foo.Instance;   // 0
            Console.WriteLine(Foo.X); // 3
            //原因是:静态字段按声明顺序初始化

            //wait
            Console.ReadKey();
        }
    }
}

const常量与readonly只读量

const常量

声明和定义常量成员:

class Panda
{
    public const string Hobby = "girl";
}

声明和定义局部常量:

public void SomeMethod()
{
    const int panda = 666;
}

使用类型成员常量:

using System;

namespace Test
{
    class Panda
    {
        public const string Hobby = "girl";

        public void SomeMethod()
        {
            //在类方法中使用常量
            Console.WriteLine(Panda.Hobby);
        }
    }

    class Program
    {
        static void Main()
        {
            //方法调用
            Panda obj = new Panda();
            obj.SomeMethod();

            //在类外部使用常量
            Console.WriteLine(Panda.Hobby);
            
            Console.ReadKey();
        }
    }
}

注意:

​ 常量在声明时必须初始化,在编译期就必须确定其值

​ 值类型可以声明为常量,但引用类型不可以声明为常量,除非编译期就确定值

string类型是一个例外,作为引用类型,也可以作为常量

​ 常量在声明后不能改变

​ 常量表现为静态量,没有实例也可以直接使用,但常量不可以直接声明为static

​ Constant fields are static automatically

​ 调用常量的方法:类.常量名

​ 常量与静态量不同,常量没有存储位置,编译后替换为对应值

​ 常量不仅可以作为类型成员,还可以是局部变量

readonly只读量

说明:只可以读取不可以修改

实例:

class Dog
{
    readonly public int code;
    readonly public string name;
    readonly public decimal price;
    public Dog()
    {
        code = 666;
        name = "Kitty";
        price = 666.66M;
    }
}

static readonly静态只读量

可以 直接赋值 或者 静态构造函数内 赋值

直接赋值:

class Panda
{
    public static readonly string Hobby = "girl";
}

静态构造函数内赋值:

class Panda
{
    public static readonly string Hobby;
    static Panda()
    {
        Panda.Hobby = "girl";
    }
}

const与readonly对比

只读量可以在运行时赋值、在构造函数内 或者 直接赋值,常量必须在声明时赋值

只读量是在运行时赋值,常量在编译期就进行赋值

只读量可以是静态也可以不是静态,常量只能是静态的

只读量需要在内存中存储,而常量最终替换成对应值

只读量只可以作为类型成员,常量可以是类型成员也可以是局部变量

无法定义成const常量的类型可以使用static readonly来定义静态只读量

//不可以,常量必须在编译期确定值
public const TextWriter textWriter = File.CreateText("D:/test.txt");
//使用static radonly进行修饰即可
public static readonly TextWriter textWriter = File.CreateText("D:/test.txt");

由于常量最终被编译器替换成直接量。在跨程序集维护程序时,很容易发生不一致的问题,为此尽量使用static readonly静态只读成员 代替 const成员

class PandaClass
{
   public readonly TextWriter p = File.CreateText("D:/test.txt");
}

默认操作符default()

给予成员指定类型的默认值

class TestClass
{
    public int x = default(int);
    public float y = default(float);
    public string s = default(string);
    public char c = default(char);
    public bool b = default(bool);
    public decimal d = default(decimal);
}

C#7.1起如果编译可以自动推断出类型,可以免去写类型

class TestClass
{
    public int x = default;
    public float y = default;
    public string s = default;
    public char c = default;
    public bool b = default;
    public decimal d = default;
}

同时声明多个字段(DECLARING MULTIPLE FIELDS TOGETHER)

static readonly int legs = 8,
                    eyes = 2;

函数成员 - 方法(methods)

说明

一条或多条可复用的语句组成的逻辑结构,可以传入、传出参数

语法:

[权限访问修饰符] [其他修饰符] 返回值 方法名([参数表])
{
    //code
}

使用方法原因(好处)

便于管理和维护

可以复用

方法命名规范

使用动词或动词短语

使用Pascal风格

方法内的主要内容

本地(局部)变量

流程流结构

方法调用

内嵌的块

基本方法

class Panda
{
    public void Greeting()
    {
        Console.WriteLine("Hello Everyone");
    }
}

表达式方法体(C# 6)(Expression-bodied Method)

注意:从C#6开始支持

实例:

class PandaTest
{
    public void DoSomething() => Console.WriteLine("Panda666");
    public int DoSomething2() => 666;
    public bool DoSomething3(int i, int j) => i > j; 
}

实例:

int Foo (int x) => x * 2;
void Foo (int x) => Console.WriteLine (x);

实例:Expression-bodied functions can also have a void return type

void Foo (int x) => Console.WriteLine (x);

参数注意

避免写超过5个参数的方法

如果要传递多个参数,则使用结构

参数类型区别

概述

参数修饰符(Parameter modifier) Passed by Variable must be definitely assigned
None Value Going in
ref Reference Going in
out Reference Going out
in Reference (read-only) Going in

值参数(Value Parameters)

为形参在栈上分配内存,将实参值直接复制给形参

实参不一定是变量,可能是字面量值、表达式

值类型的形参内存内分配的值为实参的值

引用类型的形参内存分配的值为实参值内的引用

public void Test(string name)
{
}

引用参数(Reference Parameters)

必须使用ref修饰形参,调用时也必须ref修饰

实参必须是变量,使用前必须赋值

不为形参在栈上分配内存,形参是实参的别名

引用类型做引用参数,实参和形参指向同一个引用

public void Test(ref string name)
{
}

输出参数(Output Parameters)

实参和形参都必须使用out修饰

使用前可以不用赋值

实参必须是变量,不可以是直接量和表达式

在方法内必须为输出参数赋值

public void Test(out string name)
{
    
}

从C# 7.0开始,支持 out变量(OUT VARIABLES) 和 丢弃变量(OUT DISCARDS VARIABLES)

out变量:在设置参数的同时定义变量

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            //定义s
            DoSomething(out string s);

            Console.WriteLine(s);

            //wait
            Console.ReadKey();
        }

        public static void DoSomething(out string s)
        {
            s = "Panda666.com";
        }
    }
}

丢弃变量:丢弃不敢兴趣的out参数

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            //定义s
            DoSomething(out string s,out _);

            Console.WriteLine(s);

            //wait
            Console.ReadKey();
        }

        public static void DoSomething(out string s, out string s2)
        {
            s = "Panda666.com";
            s2 = "Panda666.com";

        }
    }
}

注意:如果当前作用域下已经存在_变量标识符将无法使用丢弃参数

还可以设置多个丢弃out参数

SomeBigMethod (out _, out _, out _, out int x, out _, out _, out _);

Try Pattern using Out variable

DateTime.TryParse(s, out DateTime date)

参数数组(Params)

形参使用params修饰,并且必须是一个数组类型

参数数组的元素类型必须相同

一个方法最多有且只能有一个参数数组参数

参数数组必须放在方法参数列表最后

如果传入的是一个数组类型,将直接使用该数组,改变会反应到原数组

方法内使用该数组访问该参数

public void Test(string name, params string[] skills)
{
}

可选参数

[Optional]特性修饰参数即可

//引入命名空间
using System.Runtime.InteropServices;
//使用[Optional]特性修饰参数
public static void Test(string arg1, [Optional] int arg2)
{
    Console.WriteLine(arg1);
    Console.WriteLine(arg2);
}

输入参数(in)

in修饰符和ref修饰符相当类似,都是引用传递,没有值复制

只是in修饰符后的参数,进入方法后,不可以进行修改,也就是说是只读(ReadOnly)

适合于包含大量数据的值类型(比如struct)传递,减少内存复制

注意:从7.2开始支持使用in修饰参数

提示:本质还是引用复制,但引用为不可以修改

提示:常用于结构struct类型

实例1:

using System;
using System.Text;

namespace ConsoleApp1
{
   class PandaClass
   {
       public void Dosomething(in StringBuilder sb)
       {
           sb.Append("Panda");
           Console.WriteLine(sb);
       }
   }

   class Program
   {
       static void Main(string[] args)
       {
           PandaClass pandaClass = new PandaClass();
           StringBuilder stringBuilder = new StringBuilder("Panda666");
           pandaClass.Dosomething(stringBuilder);
           Console.WriteLine(stringBuilder);

           //wait
           Console.ReadKey();
       }
   }
}

实例:

using System;
using System.Collections;

namespace ConsoleApp2
{
    /// <summary>
    /// 个人信息结构体
    /// </summary>
    struct PersnalInformation
    {
        public string Code { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public string Sex { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //初始化测试数据
            PersnalInformation persnalInformation = new PersnalInformation();
            persnalInformation.Name = "Panda";
            persnalInformation.Code = "666";
            persnalInformation.Age = 666;
            persnalInformation.Sex = "Male";

            //打印个人信息
            PrintPersonalInformation(in persnalInformation);

            //wait
            Console.ReadKey();
        }

        /// <summary>
        /// 打印个人信息
        /// </summary>
        /// <param name="persnalInformation"></param>
        static void PrintPersonalInformation(in PersnalInformation persnalInformation)
        {
            Console.WriteLine($"{persnalInformation.Code}");
            Console.WriteLine($"{persnalInformation.Name}");
            Console.WriteLine($"{persnalInformation.Age}");
            Console.WriteLine($"{persnalInformation.Sex}");
        }
    }
}

输出参数更新(C# 7.0)

C# 7.0之后可以直接将声明变量和输出参数写在一起

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

namespace ConsoleApp3
{
    class TestClass
    {
        public void SomeMethod(out int i, out string s)
        {
            i = 666;
            s = "Panda666";
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            TestClass testClass = new TestClass();

            //C# 7.0之前
            int i;
            string d;
            testClass.SomeMethod(out i,out d);

            //C# 7.0.之后
            testClass.SomeMethod(out int i2, out string s2);
                   
            //wait
            Console.ReadKey();
        }
    }
}

ref局部变量引用(Ref Local)(C# 7.0)

ref局部变量引用可以定义一个变量的别名

语法:

注意:在声明处和引用处都需要使用ref关键字

注意:只可以引用局部变量、返回值、对象的字段、数组的元素

int no1 = 1;
ref int no2 = ref no1;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp3
{
    class Program
    {
        static void Main(string[] args)
        {
            int y = 666;
            //创建ref引用
            ref int x = ref y;

            Console.WriteLine($"y = {y}"); //666
            Console.WriteLine($"x = {x}"); //666
            x = 888;
            Console.WriteLine($"y = {y}"); //888
            Console.WriteLine($"x = {x}"); //888
            y = 999;
            Console.WriteLine($"y = {y}"); //999
            Console.WriteLine($"x = {x}"); //999

            //wait
            Console.ReadKey();
        }
    }
}

ref返回值(Ref Returns)(C# 7.0)

使用ref修饰返回类型,可以返回该类型值的引用

限制:

return ref不可以返回空值、常量、枚举值、类或结构的属性(字段可以)、只读指针

只可以return ref本身就是外部的变量,比如方法参数、类的字段

如果调用方法时,忽略了ref关键字,获得的将是值,而不是引用

语法:

using System;

namespace ConsoleApp3
{
    class TestClass
    {
        private int _code = 666;
        public ref int ReturnCode()
        {
            //返回_code的引用
            return ref _code;
        }

        public int Code 
        { 
            get
            {
                return this._code;
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            TestClass testClass = new TestClass();
            ref int code = ref testClass.ReturnCode();
            //输出code值
            Console.WriteLine(testClass.Code); //666
            code = 888;
            //输出code值
            Console.WriteLine(testClass.Code); //888

            //wait
            Console.ReadKey();
        }
    }
}
class Program
{
    public ref int GetFirstOddNumber(int[] numbers)
    {
        for (int i = 0; i < numbers.Length; i++)
        {
            if (numbers[i] % 2 == 1)
            {
                return ref numbers[i]; //returning as reference  
            }
        }
        throw new Exception("odd number not found");
    }
    static void Main(string[] args)
    {
        Program p = new Program();
        int[] x = { 2, 4, 62, 54, 33, 55, 66, 71, 92 };
        ref int oddNum = ref p.GetFirstOddNumber(x); //storing as reference  
        Console.WriteLine($"\t\t{oddNum}");
        oddNum = 35;
        for (int i = 0; i < x.Length; i++)
        {
            Console.Write($"{x[i]}\t");
        }
        Console.WriteLine();
        Console.WriteLine("Press any key to exist.");
        Console.ReadKey();
    }
}

静态方法

class Panda
{
    public static void Test()
    {

    }
}

可以声明为静态成员的其他类型:
image

方法支持的修饰符(Methods Modifiers)

Static modifier static

Access modifiers public internal private protected

Inheritance modifiers new virtual abstract override sealed

Partial method modifier partial

Unmanaged code modifiers unsafe extern

Asynchronous code modifier async

方法重载

说明:

​ 类中可以有一个以上的方法用相同的名称,叫做方法重载(method overload)

方法的签名由以下信息组成:

​ 方法名称

​ 参数数量

​ 参数数据类型和顺序

​ 参数修饰符

实例:

class Panda
{
    public void Abc(int i)
    {
}

    public void Abc(string i)
    {
    }
}

方法的重载在编译时就确定了,除非是dynamic参数

实例:

static void Foo (Asset a) { }
static void Foo (House h) { }
House h = new House (...);
Foo(h);                      // Calls Foo(House)
Asset a = new House (...);
Foo(a);                      // Calls Foo(Asset)
Asset a = new House (...);
Foo ((dynamic)a);   // Calls Foo(House)

参数-形参-实参

参数:传入方法的数据

实参:作为方法参数的具体数据

形参:具体数据的符号代表

参数默认值(可选参数)(Optional parameters)

为了让参数是可选的,可以为参数提供一个默认值

在定义方法的时候直接赋予参数默认值,该参数将变成可选参数,调用的时候不必进行输入

注意:

可选参数不可以使用参数修饰符,如果是引用类型默认值只能是null
​ 从.NET 4.0可用
​ 将可选参数添加到从另一个程序集调用的公共方法时,需要重新编译两个程序集

支持可选参数的类型:
image

可选参数的位置:
image

实例:

public void Test(string name = "Panda", params string[] skills)
{
    
}

命名参数(Named arguments)

说明:显式指定参数的名称和值,而不需要管参数的顺序

注意:

​ 必须在位置参数之后

​ 使用:冒号,而不是等号

​ 命名参数可以不按定义顺序

​ 从.NET 4.0可用

定义:

class Panda
{
    public void Test(string name, int age, string gender )
    {
    }
}

命名参数测试:

Panda obj = new Panda();
obj.Test(name: "Panda", gender: "man", age: 666);

分部方法

分部方法由两部分组成:定义和实现

定义通常由代码生成器生成,实现通常由手动编写

本质是 方法声明 和 实现分离

注意:分部方法必须返回void

注意:分部方法不可以使用修饰符,即默认private

注意:分部方法的参数不可以使用out修饰符

语法:

partial class TestClass
{
    //声明部分
    partial void SomeAction();
    //实现部分
    partial void SomeAction()
    {

    }
}

返回值

当返回值为void,为了提前返回,可以使用return

public void Test()
{
    if(1 == 1)
    {
        return;
    }
}

还可以返回指定的类型,比如int、string、List

public string GetFruit()
{
 	 return "Apples";
}

还可以返回tuple

public (string, int) GetFruit()
{
    return ("Apples", 5);
}
(string, int) fruit = bob.GetFruit();

返回多个值的办法

return more than one value from a method

  1. Using Custom DataType: You can return multiple values from a method by using a custom data type (i.e. class) as the return type of the method. But sometimes we don’t need or don’t want to use classes and objects because that’s just too much for the given purpose.
  2. Using Ref and Out variable: You can also return more than one value from the method either by using the “out” or “ref” parameters. Using “out” or “ref” parameters is quite difficult to understand and moreover, the “out” and “ref” parameters will not work with the async methods.
  3. Using dynamic keyword: You can also return multiple values from a method by using the dynamic keyword as the return type. The dynamic keyword was introduced in C# 4. But from a performance point of view, we probably don’t want to use dynamic.
  4. use Tuple,in .net it is equels System.ValueTuple

局部函数(local function)(LOCAL METHODS)(C# 7.0)

类似定义变量一样定义函数

局部函数类似C/C++中的函数

局部方法常见使用场景:在方法体内部简化代码

注意:局部方法只有在方法内定义和使用

注意:局部方法自C# 7.0可用

注意:局部方法不可以使用static修饰,如果父方法是static的,则其也是static的

注意:局部函数可以是迭代器(iterators )或者异步(asynchronous)的

注意:局部方法可以出现在其他函数类型中

​ 如属性访问器、构造函数等、Lambda表达式、类索引器

​ 本地方法可以是迭代器或异步方法

实例:

using System;

namespace ConsoleApp1
{
    class TestClass
    {
        public void SomeMethod()
        {
            //局部方法
            void doSomeThing()
            {
                Console.WriteLine("Panda666");
            }

            //局部方法
            void doSomething2() => Console.WriteLine("Panda666.com");

            //使用局部方法
            doSomeThing();
            doSomething2();
        }
    }
}

实例:

void WriteCubes()
{
  Console.WriteLine (Cube (3));
  Console.WriteLine (Cube (4));
  Console.WriteLine (Cube (5));

  int Cube (int value) => value * value * value;
}

静态局部方法(STATIC LOCAL METHODS)

注意:C# 8版本起开始支持

使用static修饰局部方法可以防止它看到封闭方法的局部变量和参数

这有助于减少耦合并使局部方法能够随心所欲地声明变量

而不会与包含方法中的变量发生冲突

密封方法(Sealing Function)

密封方法不可以被子类重写(Override)

注意:虽然密封方法不可以被重写,但可以被子类隐藏(new)

class Panda
{
    public seal void DoSomething()
    {

    }
}

重载方法(OVERLOADING METHODS)

注意:

params参数不是重载签名的一部分

void Goo (int[] x) {...}
void Goo (params int[] x) {...} // Compile-time error

引用参数也是签名的一部分

void Foo (int x) {...}
void Foo (ref int x) {...}   // OK so far
void Foo (out int x) {...}   // Compile-time error

输出参数也是签名的一部分

void Foo (int x) {...} 
void Foo (out int x) {...}   // OK so far

但是引用参数和输出参数不可以混合,会导致歧义

void Foo (ref int x) {...}
void Foo (out int x) {...}   // Compile-time error

函数成员 - 属性(properties)

说明

属性在类外表现为一个字段,在类内表现为一个存取函数

属性可以是只读、只写、计算的

属性本质是一个存取函数

属性本质是函数,所以做参数不可以使用ref和out修饰

属性类型

Read-only property

Write only property

Read Write property

Auto-implemented property

普通属性

image

从C# 7.0开始,可以直接使用表达式来设置get/set

class Panda
{
    private int _someData;
    public int SomeData
    {
        get => _someData;
        set => _someData = value;
    }
}

静态属性

class Panda
{
    static void Main(string[] args)
    {
        Console.WriteLine("data: {0}", Panda.Age);
        Console.ReadKey();
    }
    private static int _age = 666;
    public static int Age
    {
        get
        {
            return Panda._age;
            //code
        }
        set
        {
            //code1
            Panda._age = value;
        }
    }
}

只读、只写属性(read-only properties)(write-only properties)

image

只读属性(只有get即可)

public int Age
{
    get
    {
        return Panda._age;
        //code
    }
}

只写属性(只有set即可)

public static int Age
{
    set
    {
        Panda._age = value;
    }
}

计算属性

实例:属性的值来自其他值计算获得(CALCULATED PROPERTIES)

class PandaTest
{
    decimal currentPrice, sharesOwned;
    public decimal Worth
    {
      get { return currentPrice * sharesOwned; }
    }
}

表达式体属性(EXPRESSION-BODIED PROPERTIES)

属性可以和表达式结合起来使用

实例:只读的表达式属性

class PandaTest
{
    //表达式体属性
    public decimal Worth => currentPrice * sharesOwned;
}

实例:可读可写的表达式属性

class PandaTest
{
    public decimal Worth
    {
      get => currentPrice * sharesOwned;
      set => sharesOwned = value / currentPrice;
    }
}

自动属性(Automatic Properties)

C#编译器帮助我们自动生成后备字段,我们可以只声明属性,成为自动属性

public int Age { get; set; }

注意:自动属性不可以是只读或只写的

从C# 7.0开始可以直接给自动属性赋初值

public int SomeData2{get;set;} = 666;

属性权限

属性的get、set存取器可以使用独立的访问修饰符

class Panda
{
    private int _age;
    public int Age
    {
        private get
        {
            if(this._age < 18)
            {
                return 18;
            }
            else
            {
                return this._age;
            }
        }
        set
        {
        }
    }
}

注意:

​ 只读、只写存取器不可以使用独立访问修饰符

​ get和set中只能有一个有修饰符

​ 属性内部的访问修饰符必须比外部的访问修饰符的强度要强

属性支持的修饰符

修饰符 说明
Static modifier static
Access modifiers public internal private protected
Inheritance modifiers new virtual abstract override sealed
Unmanaged code modifiers unsafe extern

表达式形式属性(Expression-bodied Properties)

注意:只读属性(read-only property)在C# 6.0起可用

注意:set方法表示式形式在C# 7.0起可用

实例1:

class TestClass
{
    private int _code = 666;
    public int Code 
    {
        set => this._code = value;
        get => this._code;
    }
}

实例2:

public decimal Worth => currentPrice * sharesOwned;

实例3:

public decimal Worth
{
  get => currentPrice * sharesOwned;
  set => sharesOwned = value / currentPrice;
}

属性初始化(PROPERTY INITIALIZERS)

类似字段赋值方式初始化属性

public decimal CurrentPrice { get; set; } = 123;

初始化只读属性

public int Maximum { get; } = 999;

属性与字段对比

属性可以处理逻辑,字段不可以

属性可以只读或只写,字段可以只读,但不可以只写

属性和字段编译后的内容不同

属性本质是存取函数,字段本质是数据字段

属性设置初始值(Property initializers)

现在属性支持直接设置初始值了

public string Category { get; set; } = "Watersports";

而且只读属性和属性直接初始值混合使用

public bool InStock { get; } = true;

注意:只读属性的初始化即使直接设置了,也是可以在构造函数中修改的

属性在CLR中的实现(CLR PROPERTY IMPLEMENTATION)

属性在.NET CLR中将会转为为get_XXX 和 set_XXX方法

实例:

public decimal get_CurrentPrice {...}
public void set_CurrentPrice (decimal value) {...}

注意:在Windows Runtime libraries中的set_XXX方法 是以put_XXX方法实现

函数成员 - 索引器(indexers)

说明

索引器与属性类似,都是一组get和set访问器组成

与属性类似,索引器不需要内存来存储

与属性类似,索引器一般用于访问类本身的其他数据成员

属性一般只表示单个数据,索引器通常表示多个数据成员

与属性一样,索引器可以是只读或只写的

注意:索引器不可以是静态的

注意:索引器的存取器可以使用修饰符

注意:但只读或者只写索引器内部不可以使用修饰符

提示:索引器本质是函数

类索引器常见使用场景:访问类中类似字典、序列、数组

注意:索引器的索引类型可以是任何类型
image

声明索引器

注意:索引器没有名称,名称的位置放置this

提示:参数列表放置在方括号之中

注意:参数列表必须有至少一个参数

提示:索引器可以有多个参数

提示:索引器可以重载

声明索引器语法:
image

实例:

public string this [string x, int i]
{
    get
    {

    }
    set
    {
        
    }
}

set访问器

set方法的返回类型为void

set方法的参数列表和索引器声明中的相同

set方法有一个名为value的隐式参数,值和索引类型相同
image

get访问器

image

使用索引器

image

索引器与Null运算符结合使用

string s = null;
Console.WriteLine (s?[0]);  // Writes nothing; no error.

索引器与表达式体结合使用(expression-bodied syntax)

class PandaTest
{
    //可读可写索引
    public int this[int i]
    {
        get => 11;
        set => Console.WriteLine(123);
    }

    //只读索引
    public string this[string s] => s + "123";
}

实例-索引器基本使用

using System;

namespace ConsoleApp1
{
    class Panda
    {
        static void Main(string[] args)
        {
            Dog someDog = new Dog();
            Console.WriteLine("Data {0}", someDog[0]);
            Console.WriteLine("Data {0}", someDog[1]);
            Console.WriteLine("Data {0}", someDog[2]);
            someDog[0] = "NB888";
            someDog[1] = "Baby";
            someDog[2] = "Panda";
            Console.WriteLine("Data {0}", someDog[0]);
            Console.WriteLine("Data {0}", someDog[1]);
            Console.WriteLine("Data {0}", someDog[2]);
            Console.ReadKey();
        }

        
    }

    class Dog
    {
        public string code;
        public string name;
        public string master;

        public Dog()
        {
            code = "NB666";
            name = "Kitty";
            master = "Tom";
        }

        public string this [int index]
        {
            get
            {
                switch(index)
                {
                    case 0:
                        return code;
                    case 1:
                        return name;
                    case 2:
                        return master;
                    default:
                        return "";
                }
            }

            set
            {
                switch (index)
                {
                    case 0:
                        code = value;
                        break;
                    case 1:
                        name = value;
                        break;
                    case 2:
                        master = value;
                        break;
                }
            }
        }
    }
}

实例-重载索引器

using System;

namespace ConsoleApp1
{
    class Panda
    {
        static void Main(string[] args)
        {
            Dog someDog = new Dog();
            Console.WriteLine("Data {0}", someDog[0]);
            Console.WriteLine("Data {0}", someDog[1]);
            Console.WriteLine("Data {0}", someDog[2]);
            Console.WriteLine("===================");
            Console.WriteLine("Data {0}", someDog["code"]);
            Console.WriteLine("Data {0}", someDog["Name"]);
            Console.WriteLine("Data {0}", someDog["master"]);
            Console.ReadKey();
        }

        
    }

    class Dog
    {
        public string code;
        public string name;
        public string master;

        public Dog()
        {
            code = "NB666";
            name = "Kitty";
            master = "Tom";
        }

        public string this [int index]
        {
            get
            {
                switch(index)
                {
                    case 0:
                        return code;
                    case 1:
                        return name;
                    case 2:
                        return master;
                    default:
                        return "";
                }
            }
        }

        public string this [string index]
        {
            get
            {
                switch (index.ToLower())
                {
                    case "code":
                        return code;
                    case "name":
                        return name;
                    case "master":
                        return master;
                    default:
                        return "";
                }
            }
        }
    }
}

只读表达式形式索引器(read-only expression-bodied Index)

注意:从C# 6.0开始可用

public string this [int wordNum] => words [wordNum];

索引器和Range结合使用( INDICES AND RANGES WITH INDEXERS)

注意:索引器和Range结合从C#8开始支持

实例1:

//定义索引器
public string this [Index index] => words [index];
public string[] this [Range range] => words [range];
//使用索引器
Sentence s = new Sentence();
Console.WriteLine (s [^1]);         // fox  
string[] firstTwoWords = s [..2];   // (The, quick)

索引器在CLR内部实现(CLR INDEXER IMPLEMENTATION)

索引器在CLR底层的实现(CLR INDEXER IMPLEMENTATION)

索引器自CLR底层会转为get_Item方法和set_Item方法

索引器在CLR内部转为get_Item 和 set_Item方法形式

public string get_Item (int wordNum) {...}
public void set_Item (int wordNum, string value) {...}

函数成员 - 构造函数(constructors)

构造器的类型

  1. Default Constructor
  2. Parameterized Constructor
  3. Copy Constructor
  4. Static Constructor
  5. Private Constructor

普通构造函数

作用:初始化类的实例

注意:如果需要在外部创建类实例,要把构造函数声明为public

提示:如果类没有定义构造函数,编译器将默认添加一个无参无内容的构造函数

提示:构造函数没有返回值,也不需要声明

提示:构造函数可以重载

提示:构造函数可以互相调用

提示:构造函数可以带有参数

注意:构造函数可以是非public的

class Dog
{
    public Dog()
    {
    }
}

构造函数可以使用的修饰符

image

实例:非public构造函数的使用场景,使用静态函数来初始化该类

public class Class1
{
  Class1() {}     // Private constructor
  public static Class1 Create (...)
  {
    // Perform custom logic here to return an instance of Class1
  }
}

单表达式构造函数(single-statement constructors)

注意:从C# 7开始可用

public Panda (string n) => name = n;

构造函数调用构造函数

image

继承与构造函数

构造函数先后顺序
image

调用基类构造函数

注意:构造函数不会被继承,但非private构造函数可以被子类调用

隐式自动调用基类构造函数:
image

显式手动调用基类构造函数:
image

静态构造函数(Static Constructors)

静态构造函数一般用于初始化一个类,由运行时(runtime)自动调用

静态构造函数对每个类型执行一次,而不是对每个实例执行一次

调用时间:使用任何静态成员之前、定义该类的实例

注意:不能重载,一个类只能有一个静态构造函数

注意:不能有修饰符和参数

注意:只执行一次

注意:不需要显式调用,系统默认调用

注意:如果静态构造函数抛出未处理的异常,则该类型在应用程序生命周期内不可用

注意:静态字段只会在使用之前才执行初始化

静态构造函数支持的修饰符:unsafe extern

实例:

class Dog
{
    public static int code;
    static Dog()
    {
        Dog.code = 666;
    }
}

实例:调用时机

class Program
{
    static void Main() { Console.WriteLine (Foo.X); }   // 3
}

class Foo
{
    public static Foo Instance = new Foo();
    public static int X = 3;
    //因为静态成员X并未被使用,所以为0
    Foo() { Console.WriteLine (X); }   // 0
}

注意:

如果静态构造函数抛出一个未处理的异常,将导致应用不可以用

构造函数与可选参数

结合构造函数与可选参数的场景之一:给只读成员赋值

构造函数重载

构造函数重载后,在编译期就会确定使用哪个构造函数

如果参数是dynamic的,则在运行时才确定用哪个构造函数

函数成员 - 析构函数(Finalizer)

说明

用于类在销毁之前执行清理或释放非托管资源的行为

析构函数在生成的IL代码中本质是Finalize()方法

注意:

类的解构方法可以重载

类的解构方法可以是扩展方法(extension method)

析构器只能用于类,用于在GC回收内存之前执行操作的方法

protected override void Finalize()
{
    try
    {
        //code
    }
    finally
    {
        base.Finalize();
    }
}

定义

析构器支持的修饰符

Unmanaged code modifier unsafe

实例:

class Panda
{
    ~Panda()
    {
        //code
    }
}

在编译器内部实现

本质是override了Object对象的Finalize方法

在编译器内部将会把析构函数转为以下形式:

protected override void Finalize()
{
  //code...
  base.Finalize();
}

解构函数可以有out参数

实例:

//定义类型
class Rectangle
{
  public readonly float Width, Height;

  public Rectangle (float width, float height)
  {
    Width = width;
    Height = height;
  }

  public void Deconstruct (out float width, out float height)
  {
    width = Width;
    height = Height;
  }
}
//使用类型
var rect = new Rectangle (3, 4);
(float width, float height) = rect;          // Deconstruction
//或者 var (width, height) = rect;
//还可以跳过不需要的参数,var (_, height) = rect;
Console.WriteLine (width + " " + height);    // 3 4

单行析构函数体形式(single-statement finalizers)(expression-bodied)

~Class1() => Console.WriteLine ("Finalizing");

注意

每个类只能有一个析构函数

析构函数没有参数、没有返回值、没有修饰符

析构函数不能显式调用

析构函数无确切运行时间

没有静态析构函数

自己释放资源办法

使用IDisposable接口,该接口有一个Dispose()方法

interface IDisposable
{
    void Dispose();
}

因为我们并不知道系统何时调用析构函数,所以我们可以实现IDisposable接口,再Dispose()方法内实现释放资源的代码,调用Dispose()实现自己释放资源。然后在析构函数内也要调用该方法,以防止自己没有调用该方法释放资源

注意:在Dispose()方法的最后应该调用GC.SuppressFinlize()方法,通知CLR不需要调用该对象的析构函数了,因为在Dispose()方法内已经完成清理工作了

Dispose()方法与析构函数对比:
image

实例:
image

实现析构器的建议

确保析构器能够快速执行完成

最好不要使析构器进行阻塞

最好不要引用其他可析构对象

最好不要在析构函数内抛出异常

Close()方法与Dispose()方法

一些类型的实例同时包含Close()和Dispose()

Close方法内部只是简单的调用Dispose方法而已

在析构器内调用Dispose(Calling Dispose from a Finalizer)

一种流行的模式是让析构器调用Dispose

当不紧急进行清理并通过调用Dispose加快清理速度时

这更多的是优化而非必要

实现方式:

class Test : IDisposable
{
  public void Dispose()             // NOT virtual
  {
    Dispose (true);
    GC.SuppressFinalize (this);     // Prevent finalizer from running.
  }

  protected virtual void Dispose (bool disposing)
  {
    if (disposing)
    {
      // Call Dispose() on other objects owned by this instance.
      // You can reference other finalizable objects here.
      // ...
    }

    // Release unmanaged resources owned by (just) this object.
    // ...
  }

  ~Test() => Dispose (false);
}

函数成员 - 自定义类型转换(Custom Implicit and Explicit Conversions)

说明

支持自定义 隐式 和 显式 转换

必须使用public、static修饰符

Implicit and explicit conversions are overloadable operators

注意:自定义显式和隐式转换将不会被as和is所使用

如何选择显式和隐式转换

如果转换不会损失信息和精度,优先考虑使用隐式转换

否则使用显式转换

自定义隐式转换:确保没有精度丢失,并且可以完成

自定义隐式转换

image

注意:目标类型可以是类型泛型,比如:PandaAnimal

注意:

DO NOT provide an implicit conversion operator if the conversion is lossy

DO NOT throw exceptions from implicit conversions

自定义显式转换

image

注意:显式转换在使用时需要使用强制转换符
image

实例1:基本操作

image

实例2:隐式转换操作

using System;

namespace PandaNamespace
{
    public class PandaClass
    {
        public int Code { get; set; }

        public static implicit operator int(PandaClass pandaClass)
        {
            return pandaClass.Code;
        }
    }

    class PandaProgram
    {
        static void Main(string[] args)
        {
            PandaClass pandaClass = new PandaClass();
            pandaClass.Code = 666;
            int code = pandaClass;
            Console.WriteLine(code);

            Console.ReadKey();
        }
    }
}

注意

只能在类和结构内才可以定义类型转换

is和as运算符会忽略自定义转换

实例:无法被as和is使用

Console.WriteLine (554.37 is Note); // False
Note n = 554.37 as Note; // Error

隐式/显式转换与表达式体

实例:隐式转换与表达式体结合使用

public static implicit operator double (Note x)
=> 440 * Math.Pow (2, (double) x.value / 12 );

实例:显式转换与表达式体结合使用

public static explicit operator Note (double x)
=> new Note ((int) (0.5 + 12 * (Math.Log (x/440) / Math.Log(2) ) ));

多重转换问题

默认我们定义的自定义转换是这样的,从A类型转为B类型
image

在实际使用过程中,外部类型可能需要转为A类型后再交给用户自定义转换,同样,用户自定义转为B类型后,可能需要再转为其他类型,如图所示
image

最佳策略

要在弱相关类型之间转换,以下策略更合适:

编写一个构造函数,该构造函数具有要从中转换的类型的参数

编写ToXXX和(静态)FromXXX方法以在类型之间转换

函数成员 - 运算符重载(Operator Overloading)

说明

除了普通的运算符,还可以重载:

Implicit and explicit conversions (with the implicit and explicit keywords)

The true and false operators (not literals)

语法

运算符重载只能用在 类 和 结构 类型上

声明必须同时带有public和static修饰

一元运算符重载方法必须带一个单独class或struct类型的参数

二元运算符重载方法带两个参数,其中至少有一个必须是 class 或 struct 类型

注意:当实现了基本运算符后,复合赋值运算符,比如:+=, /=将会自动实现重载

语法

//单个操作数
public static 返回值 operator 运算符(Panda a)
{
    return -a.x;
}
//多个操作数
public static 返回值 operator 运算符(Panda a, Panda b)
{
    return a.x - b.x;
}

支持重载的运算符

以下运算符支持重载:
image

注意:

​ 运算符重载无法改变运算符的优先级和结合性

​ 运算符重载不可以创建新的运算符

运算符重载限制

(== !=), (< >), (<= >=)这些运算符必须同时成对重载

如果重载了==和!=运算符,通常需要重写方法的Equals方法和GetHashCode方法

目的是集合类型可以正常的工作

如果重载了(< >)和(<= >=)必运算符,通常需要实现IComparable方法 和 IComparable方法

实例:基本操作

using System;

namespace Test
{
    class Panda
    {
        public int x;
        public Panda(int x = 0)
        {
            this.x = x;
        }

        public static int operator +(Panda a,Panda b)
        {
            return a.x + b.x;
        }

        public static int operator -(Panda a)
        {
            return -a.x;
        }

        public static int operator -(Panda a, Panda b)
        {
            return a.x - b.x;
        }
    }

    class Program
    {
        static void Main()
        {
            Panda obj1 = new Panda(10);
            Panda obj2 = new Panda(15);

            Console.WriteLine(obj1 + obj2); //25
            Console.WriteLine(-obj1);       //-10
            Console.WriteLine(obj1-obj2);   //-5

            Console.ReadKey();
        }
    }
}

重载相等与比较运算符(Overloading Equality and Comparison Operators)

这些运算符必须同时定义:(== !=), (< >), (<= >=)

这些方法必须override:Equals, GetHashCode

必须实现这些接口:IComparable, IComparable

重载true和false(Overloading true and false)

实例:重载true

public static bool operator true (SqlBoolean x)
=> x.m_value == True.m_value;

实例:重载false

public static bool operator false (SqlBoolean x)
=> x.m_value == False.m_value;

实例2:与基本运算符进行运算

using System;

namespace PandaNamespace
{
    class PandaClass
    {
        public PandaClass(int code)
        {
            this.Code = code;
        }
        public int Code { get; set; }
        public static int operator +(PandaClass pandaClass, int i)
        {
            return pandaClass.Code + i;
        }
    }

    class PandaProgram
    {
        static void Main(string[] args)
        {
            PandaClass pandaClass = new PandaClass(666);

            int result = pandaClass + 666;
            Console.WriteLine(result);
            Console.ReadKey();
        }
    }
}

实例3:重载了==和!=运算符

public static bool operator == (Note n1, Note n2)
  => n1.value == n2.value;

public static bool operator != (Note n1, Note n2)
  => !(n1.value == n2.value);

public override bool Equals (object otherNote)
{
  if (!(otherNote is Note)) return false;
  return this == (Note)otherNote;
}
// value's hashcode will work for our own hashcode:
public override int GetHashCode() => value.GetHashCode();

运算符重载与表达式体

实例:运算符重载与表达式体结合使用

public static Note operator + (Note x, int semitones)
 => new Note (x.value + semitones);

运算符与方法对比

优先考虑使用方法,而不是运算符

与方法不同,运算符不出现在类型的智能感知列表

运算符在一般情况下没有方法直观

部分语言编译器不支持运算符重载

函数成员 - 事件(events)

事件可以是static的

详细请看事件单独章节

函数成员 - 解构器 (C# 7)

说明

让类的实例支持解构操作,让对象为外部变量赋值

在类中定义public void Deconstruct方法即可

using System;

namespace ConsoleApp1
{
    class TestClass
    {
        public string Title { get; set; }
        public decimal Price { get; set; }

        public TestClass(string title, decimal price)
        {
            this.Title = title;
            this.Price = price;
        }

        //解构器
        public void Deconstruct(out string title, out decimal price)
        {
            title = this.Title;
            price = this.Price;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var test = new TestClass("Panda",666M);
            //解构
            (string title, decimal price) = test;

            Console.WriteLine(title);
            Console.WriteLine(price);
            //wait
            Console.ReadKey();
        }
    }
}

实例:解构器 与 解构操作 结合

class Rectangle
{
 public readonly float Width, Height;

 public Rectangle (float width, float height)
 {
   Width = width;
   Height = height;
 }

 //定义解构器
 public void Deconstruct (out float width, out float height)
 {
   width = Width;
   height = Height;
 }
}
//实例化
var rect = new Rectangle (3, 4);
// Deconstruction
(float width, float height) = rect;
// 3 4
Console.WriteLine (width + " " + height);

继承(inherit)

继承中父类和子类的初始化过程

子类的字段初始化

调用子类的构造函数

​ 调用父类的构造函数

​ 父类的字段初始化

​ 父类的构造函数执行

​ 子类的构造函数执行

注意:如果子类的构造函数不调用父类的构造函数,将会自动调用无参数的父类构造函数

实例:

public class B
{
  int x = 1;         // Executes 3rd
  public B (int x)
  {
    ...              // Executes 4th
  }
}
public class D : B
{
  int y = 1;         // Executes 1st
  public D (int x)
    : base (x + 1)   // Executes 2nd
  {
     ...             // Executes 5th
  }
}

继承语法

image

示意图:
image

注意:默认所有类隐式继承自System.Object

base关键字(base Keyword)

作用:

​ 访问基类的成员(Accessing an overridden function member from the subclass)

访问基类的构造函数(Calling a base-class constructor)

实例:

public class House : Asset
{
    //other code...
    //调用父类的成员
    public override decimal Liability => base.Liability + Mortgage;
}

屏蔽基类成员

方法:

​ 在派生类中声明与基类成员相同名称的成员

​ 告诉编译器你是故意屏蔽基类成员的,使用new修饰派生类成员

提示:也可以屏蔽静态成员

class SomeClass             //基类
{
    public string Field1;
}

class OtherClass: SomeClass //派生类
{
    public new string Field1;   //屏蔽基类同名成员
}

示意图:
image

基类成员被屏蔽后仍然可以访问基类,使用base关键字即可

class SomeClass             //基类
{
    public string Field1;
}

class OtherClass : SomeClass //派生类
{
    public new string Field1;   //屏蔽基类同名成员
    //访问基类被屏蔽的成员
    public void SomeMethod()
    {
        Console.WriteLine(this.Field1);
        //使用base关键字
        Console.WriteLine(base.Field1);
    }
}

虚方法(Virtual Function Members)和重写(Override)

说明:

​ 使用基类的引用来获得派生对象的复写方法

​ Any methods decorated with override are automatically virtual

A base class method can be overridden only if it is virtual

and the overriding method is therefore virtual as well

关键字:

virtual
override

注意:

​ 重写的成员修饰符必须相同

​ 不要在构造函数中调用虚方法,可能会导致使用未初始化的字段出现错误

​ 不可以重写static

​ Methods、properties、indexers、events都支持标记为虚方法

实例:virtual property

public class Asset
{
    public string Name;
public virtual decimal Liability => 0; 
// Expression-bodied property
}

public class House : Asset
{
    public decimal Mortgage;
    public override decimal Liability => Mortgage; //override
}

实例:基本使用

using System;

namespace Test
{
    class MyBaseClass       //基类
    {
        public virtual void Print()     //虚方法
        {
            Console.WriteLine("MyBaseClass-Print");
        }
    }

    class MyDerivedClass : MyBaseClass   //派生类
    {
        public override void Print()    //方法复写
        {
            Console.WriteLine("MyDerivedClass-Print");
        }
    }

    class Program
    {
        static void Main()
        {
            //将派生类实例赋值给基类变量
            MyBaseClass mybc = new MyDerivedClass();
            //调用复写方法
            mybc.Print();     //MyDerivedClass-Print

            Console.ReadKey();
        }
    }
}

示意图:
image

虚方法会一直追寻的最顶层的复写方法

using System;

namespace Test
{
    class MyBaseClass       //基类
    {
        virtual public void Print()     //虚方法
        {
            Console.WriteLine("MyBaseClass-Print");
        }
    }

    class MyDerivedClass : MyBaseClass   //派生类
    {
        override public void Print()    //方法复写
        {
            Console.WriteLine("MyDerivedClass-Print");
        }
    }

    class SecondDerived: MyDerivedClass
    {
        override public void Print()    //方法复写
        {
            Console.WriteLine("SecondDerived-Print");
        }
    }

    class Program
    {
        static void Main()
        {
            //将派生类实例赋值给基类变量
            MyBaseClass mybc = new SecondDerived();
            //调用复写方法
            mybc.Print();     //SecondDerived-Print

            Console.ReadKey();
        }
    }
}

示意图:
image

override重写 和 new覆写 的差异:

override重写,父类会调用子类的重写方法

new覆写,父类会调用自己的方法,子类调用覆写的方法

注意:

如果存在同名的成员,子类继承父类会覆盖的父类的成员

编译器会提示错误,可以使用new来实现覆盖

目的是告诉编译器,确实是需要覆盖,而不是偶然发生

实例:

using System;
namespace ConsoleApp1
{
    class Father
    {
        public virtual void SayHello()
        {
            Console.WriteLine("Father Say Hello");
        }
    }

    class OverrideClass : Father
    {
        public override void SayHello()
        {
            Console.WriteLine("OverrideClass Say Hello");
        }
    }

    class HideClass : Father
    {
        public new void SayHello()
        {
            Console.WriteLine("HideClass Say Hello");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Father overrideClass = new OverrideClass();
            Father hideClass = new HideClass();

            overrideClass.SayHello(); //OverrideClass Say Hello
            hideClass.SayHello(); //Father Say Hello

            //wait
            Console.ReadKey();
        }
    }
}

实例:继承覆盖父类的成员

public class A { public int Counter = 1; }
public class B : A { public new int Counter = 2; }

实例:override和new的差异

Overrider over = new Overrider();
BaseClass b1 = over;
over.Foo(); // Overrider.Foo
b1.Foo(); // Overrider.Foo

Hider h = new Hider();
BaseClass b2 = h;
h.Foo(); // Hider.Foo
b2.Foo(); // BaseClass.Foo

跨程序集继承

类之间可以在程序集跨继承

要跨程序集继承需要:

​ 将基类声明为public

​ 继承项目添加程序集引用(Visual Studio中添加引用 或 csc编译中添加引用)

禁止继承

使用sealed关键字修饰类即可定义密封类。

当类不需要扩展、方法不需要重写的情况下可以定义类为密封的。

sealed class TestClass
{
}

abstract class 和 sealed class 区别

SL NO ABSTRACT CLASS SEALED CLASS
1. A class that contains one or more abstract methods is known as an abstract class. A class from which it is not possible to derive a new class is known as a sealed class.
2. The abstract class can contain abstract and non-abstract methods. The sealed class can contain non-abstract methods; it cannot contain abstract and virtual methods.
3. Creating a new class from an abstract class is compulsory to consume. It is not possible to create a new class from a sealed class.
4. An abstract class cannot be instantiated directly; we need to create the object for its child classes to consume an abstract class. We should create an object for a sealed class to consume its members.
5. We need to use the keyword abstract to make any class abstract. We need to use the keyword sealed to make any class as sealed.
6. An abstract class cannot be the bottom-most class within the inheritance hierarchy. The sealed class should be the bottom-most class within the inheritance hierarchy.

抽象(abstract)

抽象成员

抽象成员只可以是函数成员

支持以下类型的抽象成员:方法、属性、索引、事件

注意:抽象成员只能在抽象类中定义

注意:实现抽象成员必须使用override修饰

语法:

abstract class PandaClass
{
    public abstract void Test();
}

class Dog: PandaClass
{
    public override void Test()
    {
    }
}

抽象成员与虚函数对比:

image

抽象类

说明:抽象类不可以实例化,只能作为其他类的基类

注意:

​ 抽象类可以继承自抽象类

​ 抽象类的成员可以是抽象成员也可以不是抽象成员

​ 继承了抽象类的派生类必须实现抽象类的抽象成员,记得带override修饰符

实例:继承abstract类并实现成员

public abstract class Asset
{
    // Note empty implementation
    public abstract decimal NetValue { get; }
}
public class Stock : Asset
{
    public long SharesOwned;
    public decimal CurrentPrice;
    // Override like a virtual method.
    public override decimal NetValue => CurrentPrice * SharesOwned;
}

实例:

abstract class PandaClass
{
    public abstract void Test();
    public const string arg1 = "Test1";
    public static string arg2 = "Test2";
    public abstract int prop { get; set; }
    public void MethodTest()
    {
    }
}

多态(polymorphic)

说明

引用具有多态性(polymorphic)

意味着变量可以引用其类型的子类

polymorphism means 'one interface, multiple functions'

Polymorphism can be static or dynamic

In static polymorphism, the response to a function is determined at the compile time.

In dynamic polymorphism, it is decided at run-time

静态多态(Static Polymorphism)

函数重载(Function overloading)

运算符重载(Operator overloading)

动态多态(Dynamic Polymorphism)

抽象类(abstract classes)

虚方法(virtual functions)

扩展方法(Extension Methods)

说明

用于处理类无法修改、类是密封的无法继承的情况下,扩展类的方法

扩展方法的本质:扩展方法调用在编译时被转换回普通的静态方法调用

实现

声明扩展方法的类必须为static的

扩展方法必须为static的

扩展方法第一个参数必须是this 要扩展的类型

注意:
扩展方法也可以作用于接口
扩展方法也可以是泛型的

image

实例:

using System;
namespace Panda
{
    class Panda
    {
        static void Main(string[] args)
        {
            PandaClass obj = new PandaClass();
            obj.TestMethod();
            Console.ReadKey();
        }
    }

    sealed class PandaClass
    {

    }
	
    static class ExtendPandaClass
    {
        public static void TestMethod(this PandaClass obj)
        {
            Console.WriteLine("This is Test");
        }
    }
}

命名空间规范

通常将扩展方法放在命名空间下,当加载了命名空间后才可以使用

using System;

namespace PandaUtil
{
    public static class Panda
    {
        public static bool IsPanda(this string s)
        {
            return s == "Panda";
        }
    }
}

namespace ConsoleApp1
{
    using PandaUtil;
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Panda".IsPanda());

            //wait
            Console.ReadKey();
        }
    }
}

扩展方法链式调用(Extension Method Chaining)

返回对应的类型即可实现链式调用

实例:

public static class StringHelper
{
    public static string Pluralize (this string s) {...}
    public static string Capitalize (this string s) {...}
}

string x = "sausage".Pluralize().Capitalize();
string y = StringHelper.Capitalize (StringHelper.Pluralize ("sausage"));

扩展方法优先级的歧义性(Ambiguity and Resolution)

扩展方法受到命名空间的影响,如果命名空间不在访问范围,则不可以使用

扩展方法要注意命名空间是否已经引入,没有引入无法使用

扩展方法的优先级低于实例自身的方法,如果重复了,实例方法优先

任何兼容的实例方法都将始终优先于扩展方法,即使扩展方法的参数与类型更为匹配

如果两个扩展方法具有相同的签名,则必须将扩展方法作为普通静态方法调用,以消除要调用的方法的歧义

如果一个扩展方法有更具体的参数,则更具体的方法优先

如果多个扩展方法命名冲突,有具体参数的优先,但一般使用静态方法调用

实例:实例中的方法 与 扩展方法 命名冲突

using System;

namespace PandaUtil
{
    public static class Panda
    {
        public static string PandaToUpper(this string s)
        {
            return s.ToUpper() + " Panda";
        }
    }

}

namespace ConsoleApp1
{
    using PandaUtil;
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Panda".ToUpper()); //Panda

            //wait
            Console.ReadKey();
        }
    }
}

实例:多个扩展方法命名冲突

using System;

namespace PandaUtil
{
    public static class Panda
    {
        public static bool IsPanda(this string s)
        {
            Console.WriteLine("Invoke IsPanda(this string s)");
            return s == "Panda";
        }

        public static bool IsPanda(this object s)
        {
            Console.WriteLine("Invoke IsPanda(this object s)");
            return (string)s == "Panda";
        }
    }

}

namespace ConsoleApp1
{
    using PandaUtil;
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Panda".IsPanda());

            //wait
            Console.ReadKey();
        }
    }
}

实例:定义扩展方法并使用

using System;
namespace Utils
{
    public static class StringHelper
    {
        public static bool IsCapitalized (this string s)
        {
            if (string.IsNullOrEmpty(s)) return false;
            return char.IsUpper (s[0]);
        }
    }
}

namespace MyApp
{
    //引入命名空间进行使用
    using Utils;
    class Test
    {
        static void Main() => Console.WriteLine ("Perth".IsCapitalized());
    }
}

扩展方法重名问题

如果扩展方法重名,则需要使用静态方法来调用

实例:

static class StringHelper
{
    public static bool IsCapitalized (this string s) {...}
}
static class ObjectHelper
{
    public static bool IsCapitalized (this object s) {...}
}

bool test1 = "Perth".IsCapitalized();

表达式体(Expression Bodied)

支持的成员

  1. Methods

  2. Properties

  3. Constructor

  4. Destructor

  5. Getters

  6. Setters

  7. Indexers

Expression-Bodied Method

public string GetFullName() => $"{FirstName} {LastName}";
public override string ToString() => $"{FirstName} {LastName}";
public void DisplayName() => Console.WriteLine(GetFullName());

Expression-Bodied Constructor

public class Location
{
    private string locationName;
    public Location(string name) => locationName = name;
}

Expression-Bodied Destructor

public class Destroyer
{
    public override string ToString() => GetType().Name;
    ~Destroyer() => Console.WriteLine($"The {ToString()} destructor is executing.");
}

Expression-Bodied Property

public class Location
{
    private string locationName;
    public Location(string name) => locationName = name;
    public string Name
    {
        get => locationName;
        set => locationName = value;
    }
}
public class Location
{
    private string locationName;
    public Location(string name) => locationName = name;
    public string Name => locationName;
}

Expression-Bodied Indexer

public class Sports
{
    private string[] types = {"Cricket", "Baseball", "Basketball", "Football",
                              "Hockey", "Soccer", "Tennis","Volleyball" };
    public string this[int i]
    {
        get => types[i];
        set => types[i] = value;
    }
}

Limitations of Expression Bodied

The Expression-bodied members in C# don’t support a block of code. It supports only one statement with an expression, which is allowed. If you need to use more than one statement then you may use the regular methods or properties.

Branching statements (if..else, switch) are not allowed however if..else behavior can be achieved by the ternary operator.

public string FullName() => (middleName != null) ? firstName + ” ” + middleName + ” ” + lastName : firstName + ” ” + lastName;

Loop statements (i.e. for, foreach, while, and do..while are not allowed) however in some cases, it can be replaced with LINQ queries.

public IEnumerable<int> HundredNumbersList()
{
        for (int i = 0; i < 100; i++)
        yield return i;
}

public IEnumerable<int> HundredNumbersListWithExprBody() => from n in Enumerable.Range(0, 100) select n;

System.Object对象

成员

Object()

~Object()

Equals(object)

Equals(object,object)

ReferenceEquals(object,object)

ToString()

MemberwiseClone()

GetType()

GetHashCode()

详细请看System.Object笔记和官方文档

匿名类型(Anonymous Types)

匿名类型说明

省略定义类的过程,直接定义对象

匿名类型中的成员都是只读属性

匿名类型本质:

​ 后台自动生成类,实例化类

​ 匿名类型的成员为不可变

匿名类型限制

无法控制类型的名称

字段和属性是只读的

不支持事件、自定义方法、自定义操作符重载和方法重写

默认是密封的

匿名类型注意

需要new关键字

成员不需要声明类型

匿名类型对象数组需要在new后加[]

定义匿名类型

实例:简单匿名类型对象

var panda = new {
    Name = "Panda",
    Code = 123,
    Style = "Ahaha"
};

实例:匿名数组

var arr = new[]
{
    new {Name = "Panda", Code = 666},
    new {Name = "Dog", Code = 888}
};

实例:使用外部变量

int Age = 23;
var dude = new { Name = "Bob", Age, Age.ToString().Length };
//as same
var dude = new { Name = "Bob", Age = Age, Length = Age.ToString().Length };

实例:底层使用相同的类型

var a1 = new { X = 2, Y = 4 };
var a2 = new { X = 2, Y = 4 };
Console.WriteLine (a1.GetType() == a2.GetType()); // True

实例:使用数值匿名类型

var dudes = new[]
{
    new { Name = "Bob", Age = 30 },
    new { Name = "Tom", Age = 40 }
};

变量名称映射

对于已经存在的变量,可以直接映射成类属性

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            //预设的变量
            string website = "Panda666.com";
            string[] hobby = new string[] { "Girl", "Hardware" };
            
            //匿名对象
            var panda = new { website, hobby };

            Console.WriteLine(panda.website);
            Console.WriteLine(panda.hobby[0]);

            //wait
            Console.ReadKey();
        }
    }
}

后台类型

对于属性相同的匿名类型,后台使用相同的类型

如果在同一程序集中声明的两个匿名类型实例的元素的名称和类型相同,则它们将具有相同的基础类型

如果匿名类型的形式相同的,编译系统底层会使用同一个类型来表示

实例:

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            //预设的变量
            string website = "Panda666.com";
            string[] hobby = new string[] { "Girl", "Hardware" };
            
            //匿名对象
            var panda = new { website, hobby };
            var panda2 = new { website, hobby };
            Console.WriteLine(panda.GetType() == panda2.GetType()); //true

            //wait
            Console.ReadKey();
        }
    }
}

实例:

var dude = new { Name = "Bob", Age = 23 };

编译器将其编译为:

internal class AnonymousGeneratedTypeName
{
    private string name; // Actual field name is irrelevant
    private int age; // Actual field name is irrelevant
    public AnonymousGeneratedTypeName (string name, int age)
    {
        this.name = name; this.age = age;
    }
    public string Name { get { return name; } }
    public int Age { get { return age; } }
    // The Equals and GetHashCode methods are overridden (see Chapter 6).
    // The ToString method is also overridden.
}

var dude = new AnonymousGeneratedTypeName ("Bob", 23);

匿名类型作为返回值

匿名类型作为返回值需要使用dynamic 或 object 修饰,而不是var

必须使用object或者dynamic修饰返回类型即可

注意:这样会导致没有静态约束

实例:

dynamic Foo() => new { Name = "Bob", Age = 30 }; // No static type safety.

实例:这样是不行的

var Foo() => new { Name = "Bob", Age = 30 }; // Not legal!

实例:使用dynamic

dynamic Foo() => new { Name = "Bob", Age = 30 }; // No static type safety.

实例:使用object

object Foo() => new { Name = "Bob", Age = 30 }; // No static type safety.

实例:

using System;
using System.Runtime.Versioning;

namespace ConsoleApp1
{
    class PandaClass
    {
        public dynamic DoSomething()
        {
            return new
            {
                Website = "Panda666.com",
                Hobby = new string[] { "Girl", "Hardware" }
            };
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            PandaClass pandaClass = new PandaClass();
            var result = pandaClass.DoSomething();
            Console.WriteLine(result.Website);  //Panda666.com
            Console.WriteLine(result.Hobby[0]); //Girl

            //wait
            Console.ReadKey();
        }
    }
}

匿名类型比较

==运算符是重载了的

var a1 = new { X = 2, Y = 4 };
var a2 = new { X = 2, Y = 4 };
Console.WriteLine (a1.GetType() == a2.GetType());  // True

比较方法(Equals method)也是重写了的

Console.WriteLine (a1 == a2);     // False
Console.WriteLine (a1.Equals (a2));  // True

还可以创建匿名集合

var dudes = new[]
{
 new { Name = "Bob", Age = 30 },
 new { Name = "Tom", Age = 40 }
};

重写Object

重写ToString

using System;
using System.Runtime.Versioning;

namespace ConsoleApp1
{
    class PandaClass
    {
        /// <summary>
        /// 重修ToString
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "Panda666.com";
        }

        /// <summary>
        /// 在自定义一个ToString
        /// </summary>
        /// <param name="flag">标志</param>
        /// <returns></returns>
        public string ToString(string flag)
        {
            return flag + ".Panda666.com ";
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            PandaClass pandaClass = new PandaClass();
            Console.WriteLine(pandaClass.ToString());
            Console.WriteLine(pandaClass.ToString("www"));

            //wait
            Console.ReadKey();
        }
    }
}

重写GetHashCode

重写Equal

是否为null

GetType是否相等

引用是否相等

数据是否相等

GetHashCode是否相等

重写GetHashCode

重写!= 和 ==

浅拷贝和深拷贝

浅拷贝

使用ICloneabole接口:

public interface ICloneable
{
   object Clone();
}

实例:

public class Ts: ICloneable
{
  public object Clone()
  {
    return this.MemberwiseClone();
  }
}

深拷贝

说明:

public class Ts : ICloneable
{
  public object Clone()
  {
    //新对象
    Ts inner = (Ts)this.MemberwiseClone();
    //分配新的引用类型
    //修改新对象中的引用类型指向
    //返回新对象
    return inner;
  }
}

实例:

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            TestClass a = new TestClass();
            //复制对象
            TestClass b = (TestClass)a.Clone();

            a.a.i = 66666666;
            b.a.i = 77777777;
            Console.WriteLine(a.a.i);
            Console.WriteLine(b.a.i);

            Console.ReadKey();
        }
    }
    /// <summary>
    /// 用于测试类的内部
    /// </summary>
    class inner
    {
        public inner()
        {
            i = 0;
        }
        public inner(int i)
        {
            this.i = i;
        }
        public int i;
    }
    /// <summary>
    /// 测试类
    /// </summary>
    class TestClass : ICloneable
    {
        public inner a;
        public TestClass(int i)
        {
            a = new inner(i); 
        }
        public TestClass()
        {
            a = new inner();
        }
        public object Clone()
        {
            //复制当前对象的所有值类型
            TestClass newObj = (TestClass)this.MemberwiseClone();
            //更新引用类型
            newObj.a = new inner();
            //返回新对象
            return newObj;
        }
    }
}

可比较

非泛型接口

public interface IComparable
{
  int CompareTo(object o);
}
public interface IComparer
{
  int Compare(object n1, object n2);
}

泛型接口

interface IComparable<T>
{

  int CompareTo(T o);
}
interface IComparer<T>
{
  int Compare(T)(T objectA, T objectB);
  bool Equals(T)(T objectA, T objectB);
}

实现

public class Ts : IComparable
{
    public int CompareTo(object obj)
    {
        Ts item = obj as Ts;
        if(item != null)
        {
            //code
        }
        else
        {
            throw new ArgumentException("参数不匹配");
        }
    }
}

实例

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            Car[] Garage = new Car[]
            {
                new Car(12,"asd"),
                new Car(11, "asaz"),
                new Car(666666.6666M,"panda"),
                new Car(666,"zdada")

            };
            Array.Sort(Garage);
            foreach (Car item in Garage)
            {
                item.Print();
            }
            
            Console.ReadKey();
        }
    }
    class Car:IComparable
    {
        private decimal _price;
        private string _name;
        public Car(decimal price, string name)
        {
            this._price = price;
            this._name = name;
        }
        public decimal Price
        {
            get
            {
                return _price;
            }
            set
            {
                if(value != _price)
                {
                    _price = value;
                }
            }
        }
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                if(value != _name)
                {
                    _name = value;
                }
            }
        }

        public int CompareTo(object obj)
        {
            Car a = (Car)obj;
            if (this.Price > a.Price)
                return 1;
            else if (this.Price < a.Price)
                return -1;
            else
                return 0;
        }

        public void Print()
        {
            Console.WriteLine("the Price is {0} , the name is {1}",Price,Name);
        }
    }
}

比较器

IComparable接口

BCL定义了IComparable接口,实现该接口的类可以进行自定义比较

image

该接口内的CompareTo()方法的返回值:

负数值 当前对象小于参数对象

正数值 当前对象大于参数对象

零 两个对象在比较相等

实例:
image

IComparer接口

public interface IComparer
{
  int Compare(object o1, object o2);
}

IComparer接口

public interface IComparer<T>
{
  int Compare(T o1, T o2);
}

说明:通常作为一个独立的类实现该接口

实现

namespace ConsoleApplication2
{
    //比较器1
    public class CompareItem1 : IComparer
    {
        public int Compare(object x, object y)
        {
            //code
            return 1;
        }
    }
   
    //比较器2
    public class CompareItem2 : IComparer
    {
        public int Compare(object x, object y)
        {
            //code
            return 1;
        }
    }
    
    //该类重定义比较
    public class Ts
    {
        //比较类型1
        public static IComparer CompareType1
        {
            get
            {
                return new CompareItem1();
            }
        }

        //比较类型2
        public static IComparer CompareType2
        {
            get
            {
                return new CompareItem2();
            }
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            Ts[] a = new Ts[3] { new Ts(), new Ts(), new Ts() };
            //使用比较器1
            Array.Sort(a, Ts.CompareType1);
            //使用比较器2
            Array.Sort(a, Ts.CompareType2);

            Console.ReadKey();
        }
    }
}

序列与反序列

相关特性

[Serializable]

​ 让类实例可序列化

​ 修饰类

[NonSerialized]

​ 不进行序列化

​ 修饰成员

[OnSerializing]

​ 序列化前调用的方法

​ 修饰方法

[OnSerialized]

​ 序列化后调用的方法

​ 修饰方法

[OnDeSerializing]

​ 反序列化前调用的方法

[OnDeserialized]

​ 反序列化后调用的方法

序列化格式说明

BinaryFormatter格式

说明:二进制序列化格式

说明:.NET平台跨机器边界优先选择

命名空间:System.Runtime.Serialization.Formatters.Binary;

SoapFormatterSoap格式

说明:Soap格式优先选择

命名空间:System.Runtime.Serialization.Formatters.Soap;

注意:使用需引入程序集:System.System.Runtime.Serialization.Formatters.Soap.dll

XMLSerializer

说明:XML格式序列化

说明:跨平台跨语言优先选择

注意:XML序列化要求被序列化的类必须有默认无参构造函数

命名空间:System.XML.Serialization;

专有特性:

​ [XmlAttribute]将公共字段序列化为属性,而不是子元素

​ [XmlElement]将字段或属性序列化为子元素

​ [XmlRoot]定义序列化后的Xml的根(命名空间和元素名字)(用在类上)

[XmlText]属性或字段定义为节点的文本

​ [XmlType]XML类型的名称和命名空间

类接口

​ IFormatter

​ Serialize

​ Deserialize

​ IRemotingFormatting

​ Serialize

​ Deserialize

BinaryFormatter序列化实例

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace PandaNamespace
{
    /// <summary>
    /// 测试用的汽车类型
    /// </summary>
    [Serializable]
    class Car
    {
        public int CarCode;
        public string CarName;
        //标记为不序列化
        [NonSerialized]
        public string CarBrand;
        public Car(string carName,int carCode)
        {
            this.CarCode = carCode;
            this.CarName = carName;
            this.CarBrand = "BMW";
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            //实例化对象
            Car someCar = new Car("CarBaseName",666);
            //保存文件的路径
            string filePath = @"D:\Car.dat";
            
            //创建文件流
            using (Stream fstream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write))
            {
                //实例序列化对象
                BinaryFormatter se = new BinaryFormatter();
                //序列化
                se.Serialize(fstream, someCar);
            }

            Console.ReadKey();
        }
    }
}

BinaryFormatter反序列化实例

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace PandaNamespace
{
    /// <summary>
    /// 测试用的汽车类型
    /// </summary>
    [Serializable]
    class Car
    {
        public int CarCode;
        public string CarName;
        //标记为不序列化
        [NonSerialized]
        public string CarBrand;
        public Car(string carName, int carCode)
        {
            this.CarCode = carCode;
            this.CarName = carName;
            this.CarBrand = "BMW";
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            //保存文件的路径
            string filePath = @"D:\Car.dat";
            //创建文件流
            using (Stream fstream = File.OpenRead(filePath))
            {
                //实例化序列化对象
                BinaryFormatter se = new BinaryFormatter();
                //反序列化
                Car someCar2 = (Car)se.Deserialize(fstream);
                Console.WriteLine(someCar2.CarBrand);
                Console.WriteLine(someCar2.CarName);
                Console.WriteLine(someCar2.CarCode);
            }

            Console.ReadKey();
        }
    }
}

BinaryFormatter自定义序列化操作实例

BinaryFormatter自定义反序列化操作实例

引入命名空间:System.Runtime.Serialization;
使用IDeserializationCallback接口即可

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
namespace PandaNamespace
{
    /// <summary>
    /// 测试用的汽车类型
    /// </summary>
    [Serializable]
    class Car:IDeserializationCallback
    {
        public int CarCode;
        public string CarName;
        //标记为不序列化
        [NonSerialized]
        public string CarBrand;
        public Car(string carName,int carCode)
        {
            this.CarCode = carCode;
            this.CarName = carName;
            this.CarBrand = "BMW";
        }

        public void OnDeserialization(object sender)
        {
            this.CarBrand = "DBenz";
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            //实例化对象
            Car someCar = new Car("CarBaseName",666);
            //保存文件的路径
            string filePath = @"D:\Car.dat";
            
            //创建文件流
            using (Stream fstream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write))
            {
                //实例序列化对象
                BinaryFormatter se = new BinaryFormatter();
                //序列化
                se.Serialize(fstream, someCar);
            }

            //创建文件流
            using (Stream fstream = File.OpenRead(filePath))
            {
                //实例化序列化对象
                BinaryFormatter se = new BinaryFormatter();
                //反序列化
                Car someCar2 = (Car)se.Deserialize(fstream);
                Console.WriteLine(someCar2.CarBrand);
                Console.WriteLine(someCar2.CarName);
                Console.WriteLine(someCar2.CarCode);
            }

            Console.ReadKey();
        }
    }
}

SoapFormatter实例

	class Program
	{
	    static void Main(string[] args)
	    {
	        CarBase a = new CarBase(66666,"CarBaseName");
	        string path = @"D:/CarBase.soap";
	        //序列化
	        SoapFormatterMethod(path, a);
	        //反序列化
	        CarBase b = (CarBase)DeSoapFormatterMethod(path);
	        //获得内容
	        Console.WriteLine(b.Name);
	        Console.WriteLine(b.Speed);
	        Console.ReadKey();
	    }
	    /// <summary>
	    /// 序列化
	    /// </summary>
	    /// <param name="fileName">文件名</param>
	    /// <param name="o">被序列化的对象</param>
	    static void SoapFormatterMethod(string fileName, object o)
	    {
	        SoapFormatter soap = new SoapFormatter();
	        using (Stream fileStream = new FileStream(fileName,FileMode.OpenOrCreate,FileAccess.Write))
	        {
	            soap.Serialize(fileStream,o);
	        }
	    }
	    /// <summary>
	    /// 反序列化
	    /// </summary>
	    /// <param name="fileName">文件名</param>
	    /// <returns>对象</returns>
	    static object DeSoapFormatterMethod(string fileName)
	    {
	        SoapFormatter soap = new SoapFormatter();
	        using (Stream fileStream = new FileStream(fileName,FileMode.Open,FileAccess.Read))
	        {
	            return soap.Deserialize(fileStream);
	        }
	    }
	}
XMLSerializer实例
	class Program
	{
	    static void Main(string[] args)
	    {
	        CarBase a = new CarBase(6666, "CarBase");
	        string path = @"D:/test.xml";

	        //序列化
	        XMLSerializerMethod(path, a);
	        //反序列化
	        CarBase b = (CarBase)DeXMLSerializerMethod(path);

	        //获得数据
	        Console.WriteLine(b.Name);
	        Console.WriteLine(b.Speed);
	        Console.ReadKey();
	    }
	    /// <summary>
	    /// 序列化
	    /// </summary>
	    /// <param name="fileName">文件名</param>
	    /// <param name="o">序列化的对象</param>
	    static void XMLSerializerMethod(string fileName, object o)
	    {
	        XmlSerializer ser = new XmlSerializer(typeof(CarBase));
	        using (Stream fileStream = new FileStream(fileName,FileMode.OpenOrCreate,FileAccess.Write))
	        {
	            ser.Serialize(fileStream, o);
	        }
	    }
	    /// <summary>
	    /// 反序列化
	    /// </summary>
	    /// <param name="fileName">文件名</param>
	    /// <returns>解析完成的对象</returns>
	    static object DeXMLSerializerMethod(string fileName)
	    {
	        XmlSerializer ser = new XmlSerializer(typeof(CarBase));
	        using (Stream fileStream = new FileStream(fileName,FileMode.Open,FileAccess.Read))
	        {
	            return ser.Deserialize(fileStream);
	        }
	    }
	    
	}

类相关命名约定

image

结构与类对比

相同点

结构和类一样可以实现接口

差异点

结构不可以继承,类可以继承

因为结构不可以继承,所以不可以使用多层次结构和多态性

如果需要结构可以继承,可以装箱成Object、ValueType、Enum

结构从ValueType派生,类从Object派生、

结构再栈上分配内存,一般情况下访问速度高于类在堆中分配的速度

结构的复制是值复制,速度低于类的引用复制

以下情况考虑选择使用类

占用较大的内存

字段需要初始化

需要从基类继承

需要多态行为

以下情况考虑选择使用结构

需要其行为类似基本类型(int、long)

占用较小的内容

需要使用P/Invoke方法,传入结构体

需要降低垃圾回收对程序性能的影响

字段只需要默认初始化

不需要继承基类

不需要多态行为

常见坑

将结构类型的实例赋值给引用类,将导致装箱,可能导致性能降低

记录(RECORD)类型(C#9)

C#中的==运算符默认是判断两个变量指向的是否是同一个对象,即使两个对象内容完全一样,也不相等。可以通过重写Equals方法、重写==运算符等来解决这个问题,不过需要开发人员编写非常多的额外代码。
在C#9.0中增加了记录(record)类型的语法,编译器会为我们自动生成Equals、GetHashcode等方法。

public record Test(string FirstName, string LastName);

实例:

TestRecord test1 = new TestRecord("Panda","666");
TestRecord test2 = new TestRecord("Panda","666");
Console.WriteLine(test1 == test2); //true

注意:record也是普通类,变量的赋值是引用的传递。

生成一个对象的副本可以用with关键字简化:

User u2 = u1 with { Email="test@example"};

标准释放非托管内存

public class Animal : IDisposable
{
    public Animal()
    {
        // allocate unmanaged resource
    }
    ~Animal() // Finalizer
    {
        if (disposed) return;
        Dispose(false);
    }
    bool disposed = false; // have resources been released?
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    protected virtual void Dispose(bool disposing)
    {
        if (disposed) return;
        // deallocate the *unmanaged* resource
        // ...
        if (disposing)
        {
            // deallocate any other *managed* resources
            // ...
        }
        disposed = true;
    }
}

说明:

有两种Dispose方法,分别是公有的和受保护的

开发人员将调用public的Dispose方法

调用时,非托管资源和托管资源都需要释放

带有bool参数的受保护的Dispose方法在内部用于实现资源的释放

它需要检查处理,参数和释放标志,如果析构函数已经运行,则只需要释放非托管资源

GC.SuppressFinalize(this)表示告诉GC(garbage collector)不需要执行析构函数

posted @ 2022-09-14 08:39  重庆熊猫  阅读(509)  评论(0编辑  收藏  举报