1、(类)索引器
在类中定义一个索引器
public class MyClass
{
public 返回值 this[索引类型]
{
get{需要处理的逻辑}
set{需要处理的逻辑}
}
}
此时就可以通过"MyClass[索引类型]"来访问或者赋值。
2、扩展方法
需要对基础类型进行方法扩展,可以通过定义一个静态方法,并对第一个参数使用"this"关键字,比如现在需要为整形数组类型的数据扩展一个获取第二个数据的方法,可以这样做:
public static class MyExtension
{
// 自己定义一个获取整形数组中第二个数字的扩展方法
public static int GetArraySecondNum(this int[] source)
{
return source[1];
}
}
此时,就为int[]类型的数据扩展了一个名为GetArraySecondNum()的方法,调用示例:
static void Main(string[] args)
{
int[] array = new int[] { 1, 2, 3, 4, 5 };
int res = array.GetArraySecondNum(); //调用自己扩展的方法
}
定义扩展方法时,除了第一个带this关键字的参数外,后面还可以跟其他参数,调用时带上参数即可。
3、运算符重载
运算符重载可以用于自定义非常规数值的引用类型的之间的加、减、乘、除操作,用"operator"关键字后跟运算符实现。比如说现在有一个Student类,需要实现一个计算两个学生成绩的和的操作,可以使用运算符重载以便快捷操作,示例:
public class Student
{
//分数
public int score;
//重载Student之间相加的方法,返回两者分数和
public static int operator +(Student stu1,Student stu2)
{
return stu1.score + stu2.score
}
}
调用:
static void Main(string[] args)
{
Student stu1= new Student();
Student stu2= new Student();
int sumScore=stu1 + sutu2;
}
运算符重载方法必须是Public和Static的,并且参数必须是当前所属类的类型。只有一部分运算符支持重载。
扩展:自定义类型的显示/隐式转换
除了C#中已内置的各种数据类型之间的隐式转换以及父类子类之间的显示转换外,我们还可以自制自定义类之间的显示/隐式转换,也是使用了运算符重载原理,示例:
//定义一个类
public class Human
{
public int workHours = 8;
}
--------------------------------------------------------------
//定义另一个类
public class Bull
{
public int workHours = 4;
}
--------------------------------------------------------------
static void Main(string[] args)
{
//声明一个人类
Human human1 = new Human();
//显示转换为Bull类-此时由于这两个类之间没有继承关系,所以无法显示的转换,编辑器报错
Bull aBull = (Bull)human1;
}
此时为需要被显示转换的类(Human类)添加一个"显示转换操作符"方法:
public class Human
{
public int workHours = 8;
// 增加一个显示类型转换方法
// public static explicit operator 为关键字
// Bull 是转换的目标类型
// 参数类型必须是当前所处类类型
public static explicit operator Bull(Human par)
{
//声明返回值
Bull res = new Bull();
//写自己的转换逻辑(如何转换)
//res.workHours += a.workHours;
//返回Bull(目标类型)的实例
return res;
}
}
--------------------------------------------------------------------
public class Bull
{
public int workHours = 4;
}
--------------------------------------------------------------------
static void Main(string[] args)
{
//声明一个人类
Human human1 = new Human();
//此时可以成功的显示转换!
Bull aBull = (Bull)human1;
}
其中,explicit关键字定义了一个显示转换方法。
同理,如果把explicit替换为implicit,就得到了一个自定义的隐式转换的方法。
4、匿名类型
在C#中,可以通过操作符new来声明一个匿名类型,此时若用一个变量引用该类型,则该变量需要声明为var类型,这也是var在C#中最显著的功效之一,示例:
static void Main(string[] args)
{
var myType = new { name="ss",age=3};
Console.WriteLine(myType.GetType().Name);
//此时打印结果为 <>f__AnonymousType0`2
}
5、操作符"~"和"-"解释
首先明确:计算机存储值都是是以补码形式存储
"~"表示按位取反,是指对该数字内存中储存的补码取反码操作(注意这里并不是对二进制原码取反,而是对内存中的补码),此时取反码操作是忽略符号位的,但是读取时是正常读取(会根据读取的需求判断是否算上符号位)
"-"取相反数,取相反数操作都是在带符号的数值上进行的,也就是说把补码的第一位的0换成1或者1换成0,剩余位数上正常转换为原码(补码转换为原码:按位取反然后加1)
可以看出,取相反数操作"~"重点只在于对每一位上的0和1取反
6、关于List的深度复制
原理:由于List类型本身是引用类型,所以在复制List的时候我们需要复制List里每一个元素的并添加到新的List中,才能实现真正意义上的复制(深拷贝)。如果只是把原有的List赋值给一个新的变量,这只是复制了List在堆中的地址,但实际引用的还是原本的List(浅拷贝)
①如果是值类型的List可以通过数组的Copy()方法来实现深度复制
// 原数组是list1
List<int> list1 = new List<int> { 1, 2, 3, 4, 5 };
// 使用 List 的初始化器方法声明新集合时将 list1 初始化给list2
List<int> list2 = new List<int>(list1);
②如果是引用类型的List,可以通过LINQ的方法
//假设原始列表为 list1
List<object> list2 = list1.Select(item => item.Clone()).ToList();
7、装箱和拆箱
装箱和拆箱是发生在值类型和引用类型之间,两个类型之间相互转换的过程就是装箱拆箱。
装箱:值类型转换为引用类型——此过程中值从栈中拷贝到堆上
拆箱:引用类型转换为值类型——此过程中值又从堆上拷贝到了栈上
装箱和拆箱操作都发生了数据在堆、栈之间的转移,故装箱和拆箱会带来性能的损失。
8、 "is" 和 "as" 的使用
"is"比较符常用于比较一个变量是否属于指定引用类型,返回值是bool类型的值,示例:
static void Main(string[] args)
{
AAA aaa = new AAA();
bool res = aaa is AAA; //结果为true
AAA aaa = new BBB();
bool res = aaa is AAA; //结果为true
bool res = aaa is CCC; //结果为false
BBB bbb = null;
bool res = bbb is AAA; //结果为false
bool res = bbb is BBB; //结果为false
}
-------------------------------------------------------------
//考虑有以下继承关系的类
public class AAA { ... }
public class BBB:AAA
{
....
}
public class CCC:BBB
{
....
}
可以看出,当一个变量 x 用"is"和一个类型 TargetClass 比较时,如果 x 所引用的类型是 TargetClass 类型或者是其派生类型时结果都是True,和 x 声明时的数据类型关系不大(只考虑父类和派生类之间自然的隐式转换规则时)。
"as"是强制类型转换操作符,用于引用类型之间的安全的强制转换,示例:
//现有一个自定义的类
class MyClass {...}
//有一个方法是根据一个Object类型参数获取一个MyClass的值
public MyClass GetMyClass(Object value)
{
MyClass res = value as MyClass; //强制转换为MyClass类型
if (res!=null)
//res成功转换
else
//res为空-- 未能成功转换
}
若强制转换成功,则正确赋值;若强转失败,则为 null ,但是并不会引发错误(抛出异常),这和使用(T)的显示类型转换操作符不同,(T)转换失败则会抛出异常(笔记第3条解释了如何自定义一个显示转换),而使用 "as" 强制转换是相对安全的。
9、关于 "委托"
委托是用来指向方法的,不同的是,委托可以指向一个或多个方法(多播委托),当调用委托时,相当于按顺序调用委托指向的所有方法,所以委托也可以看做装 方法们 的盒子,可以往委托里装一个方法或者多个方法亦或者什么也不装。
10、关于 "事件"
两个概念:
①"事件"是基于委托的
②"事件"只是委托字段的包装器。类似于属性是对字段的包装。
所以,"事件" 只是一个包装器,它不是一个委托的字段。
"事件" 最难的部分就是声明事件。声明一个事件其实是对(实例中的)一个委托字段的封装,限制了外界对该委托字段的使用,让外界只能对该委托字段进行 "添加(方法)" 和 "删除(方法)" 操作,仅把调用该委托的权限交给实例类本身,这和属性对字段的包装非常相似。
所以,绝对不要把 "事件" 理解为 特殊的委托(这对理解 "事件" 非常重要),它就是一个委托类型字段的包装器、蒙版,从而限制外界的访问,仅此而已。