C#教程 - 类类型(Class Type)
更新记录
转载请注明出处:https://www.cnblogs.com/cqpanda/p/16678364.html
2022年9月14日 发布。
2022年9月10日 从笔记迁移到博客。
C# 中类的类型
- Abstract class
- Concrete class
- Sealed class
- Partial Class
- 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
- 嵌套内部类可以访问外部类的所有成员
- 内部类型可以访问封闭类型的私有成员
- 所有的类型都可以被嵌套,但只有类和结构可以包含嵌套类型
嵌套类与外部类的访问性:
实例:
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;
}
实例化类
实例化类在内存中示意图
定义了实例变量,在栈上分配了内存,但未在堆中分配内存
为实例变量赋值后,在堆上分配空间,并将栈上的引用指向该堆空间
基本实例化
class Panda
{
}
Panda obj = new Panda();
对象初始化器(Object Initializers)
在类创建实例的时候,在尾部表达式放置一组成员初始化语句,然后再语句中设置字段和属性值
对象初始化语法:
注意:对象初始化的成员必须使用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).可在 类内部访问 或 继承的类中访问(并集关系) |
权限对比:
public与private示意图:
protected internal可见性:
internal可见性:
类类型支持public和internal修饰符
标记为 internal 的类只能在 程序集 内可见
需要跨程序集访问需要将类声明为public
注意:
类类型成员不带修饰符默认是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
数据成员 - 字段(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()
{
}
}
可以声明为静态成员的其他类型:
方法支持的修饰符(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可用
将可选参数添加到从另一个程序集调用的公共方法时,需要重新编译两个程序集
支持可选参数的类型:
可选参数的位置:
实例:
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
- 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.
- 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.
- 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.
- 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
普通属性
从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)
只读属性(只有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访问器组成
与属性类似,索引器不需要内存来存储
与属性类似,索引器一般用于访问类本身的其他数据成员
属性一般只表示单个数据,索引器通常表示多个数据成员
与属性一样,索引器可以是只读或只写的
注意:索引器不可以是静态的
注意:索引器的存取器可以使用修饰符
注意:但只读或者只写索引器内部不可以使用修饰符
提示:索引器本质是函数
类索引器常见使用场景:访问类中类似字典、序列、数组
注意:索引器的索引类型可以是任何类型
声明索引器
注意:索引器没有名称,名称的位置放置this
提示:参数列表放置在方括号之中
注意:参数列表必须有至少一个参数
提示:索引器可以有多个参数
提示:索引器可以重载
声明索引器语法:
实例:
public string this [string x, int i]
{
get
{
}
set
{
}
}
set访问器
set方法的返回类型为void
set方法的参数列表和索引器声明中的相同
set方法有一个名为value的隐式参数,值和索引类型相同
get访问器
使用索引器
索引器与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)
构造器的类型
- Default Constructor
- Parameterized Constructor
- Copy Constructor
- Static Constructor
- Private Constructor
普通构造函数
作用:初始化类的实例
注意:如果需要在外部创建类实例,要把构造函数声明为public
提示:如果类没有定义构造函数,编译器将默认添加一个无参无内容的构造函数
提示:构造函数没有返回值,也不需要声明
提示:构造函数可以重载
提示:构造函数可以互相调用
提示:构造函数可以带有参数
注意:构造函数可以是非public的
class Dog
{
public Dog()
{
}
}
构造函数可以使用的修饰符
实例:非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;
构造函数调用构造函数
继承与构造函数
构造函数先后顺序
调用基类构造函数
注意:构造函数不会被继承,但非private构造函数可以被子类调用
隐式自动调用基类构造函数:
显式手动调用基类构造函数:
静态构造函数(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()方法与析构函数对比:
实例:
实现析构器的建议
确保析构器能够快速执行完成
最好不要使析构器进行阻塞
最好不要引用其他可析构对象
最好不要在析构函数内抛出异常
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所使用
如何选择显式和隐式转换
如果转换不会损失信息和精度,优先考虑使用隐式转换
否则使用显式转换
自定义隐式转换:确保没有精度丢失,并且可以完成
自定义隐式转换
注意:目标类型可以是类型
注意:
DO NOT provide an implicit conversion operator if the conversion is lossy
DO NOT throw exceptions from implicit conversions
自定义显式转换
注意:显式转换在使用时需要使用强制转换符
实例1:基本操作
实例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类型
在实际使用过程中,外部类型可能需要转为A类型后再交给用户自定义转换,同样,用户自定义转为B类型后,可能需要再转为其他类型,如图所示
最佳策略
要在弱相关类型之间转换,以下策略更合适:
编写一个构造函数,该构造函数具有要从中转换的类型的参数
编写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;
}
支持重载的运算符
以下运算符支持重载:
注意:
运算符重载无法改变运算符的优先级和结合性
运算符重载不可以创建新的运算符
运算符重载限制
(== !=), (< >), (<= >=)这些运算符必须同时成对重载
如果重载了==和!=运算符,通常需要重写方法的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
}
}
继承语法
示意图:
注意:默认所有类隐式继承自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; //屏蔽基类同名成员
}
示意图:
基类成员被屏蔽后仍然可以访问基类,使用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();
}
}
}
示意图:
虚方法会一直追寻的最顶层的复写方法
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();
}
}
}
示意图:
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()
{
}
}
抽象成员与虚函数对比:
抽象类
说明:抽象类不可以实例化,只能作为其他类的基类
注意:
抽象类可以继承自抽象类
抽象类的成员可以是抽象成员也可以不是抽象成员
继承了抽象类的派生类必须实现抽象类的抽象成员,记得带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 要扩展的类型
注意:
扩展方法也可以作用于接口
扩展方法也可以是泛型的
实例:
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)
支持的成员
-
Methods
-
Properties
-
Constructor
-
Destructor
-
Getters
-
Setters
-
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接口,实现该接口的类可以进行自定义比较
该接口内的CompareTo()方法的返回值:
负数值 当前对象小于参数对象
正数值 当前对象大于参数对象
零 两个对象在比较相等
实例:
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);
}
}
}
类相关命名约定
结构与类对比
相同点
结构和类一样可以实现接口
差异点
结构不可以继承,类可以继承
因为结构不可以继承,所以不可以使用多层次结构和多态性
如果需要结构可以继承,可以装箱成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)不需要执行析构函数
本文来自博客园,作者:重庆熊猫,转载请注明原文链接:https://www.cnblogs.com/cqpanda/p/16678364.html