知识点02

C#
本文总阅读量:

属性的本质是字段+方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace ClassLibrary1
{
public class Person
{
public string Name { get; set; }
}
}

namespace ClassLibrary1
{
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;

public class Person
{
[CompilerGenerated, DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string <Name>k__BackingField;

public string Name { get; set; }
}
}

索引器的本质是名为Item的属性,并且索引器可以重载,不能再写一个名为item的属性

1
2
3
4
5
6
7
8
9
10
public string this[int index]
{
get { return Names[index]; }
set { Names[index] = value; }
}

public string Item[int index]{
set_Item(int,string):void
get_Item(int):string
}

面向对象的三大特性:封装,继承,多态

类:类是现实物体的抽象,是一种数据类型,由用户定义,由构造函数,字段,属性,事件,方法组成

对象:对象是具体的,包含数据的,类的具体实例。

全局(静态)变量,编译器会赋默认值,字段必须使用前赋值,局部变量优先被使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Program
   {
       //全局变量可以不初始化直接使用,引用类型默认值是null,值类型是0
       private static string str;
       //字段使用前必须初始化
       private string str3;

       static void Main(string[] args)
       {
           string str2 = null;
           Console.WriteLine(str2);

           //string str = "luox78";局部变量会被优先使用
           Console.WriteLine(str);

           //Console.WriteLine(str3);报错
           Console.ReadKey();
       }
   }

LSP里氏替换原则:父类可以指向子类创建的对象

方法签名一般指方法名和参数,不包含返回值


访问修饰符

public 无限制

protected 子类可以访问

private 本类才能访问

internal 当前程序集内部,跨项目引用就不可以了

protected internal 同时具备两者性质,或的关系

类里面不写访问修饰符默认是private

类本身不写访问修饰符默认是internal

直接在命名空间下的类只能定义成public和internal


构造函数:

构造函数不能被继承

通过this调用自身构造函数

1
2
3
4
public Student(int height):this()//this用来调用自己类里面的构造函数
            {
                this.Height = height;
            }

通过base调用父类构造函数

1
2
3
4
public Student(int sid ,string name,int age):base(name,age)
            {
                this.SID = SID;
            }

多态

  • 通过virtual关键词实现重载,子类通过override重写方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Person
    {
    public virtual void DoSomething()
    {
    Console.WriteLine("Person做的");
    }
    }

    public class Student : Person
    {
    public override void DoSomething()
    {
    //base.DoSomething();
    Console.WriteLine("Student做的");
    }
    }

    base.DoSomething();会将父类方法也调用

  • abstract,子类重写父类中的方法。

    抽象类特点:

    1. 抽象类不能实例化 用于继承
    2. 抽象成员不能有任何实现
    3. 抽象成员必须包含在抽象类中
    4. 抽象类可以有实例成员,也可以有抽象成员
    5. 子类必须重写抽象方法
  • 接口,实现接口的类,将接口中的方法实现


静态构造函数

类中的静态变量初始化默认使用的静态构造函数

1
2
3
4
5
6
7
8
9
10
public class Class1
{
public static string name = "luox78";
}

//.cctor
static Class1()
{
name = "luox78";
}

静态构造函数特点:

  1. 类中的静态成员在第一次使用类的时候进行初始化
  2. 静态构造函数只执行一次无论是对象初始化还是直接给静态变量赋值,静态构造函数都只执行一次
  3. 静态构造函数也不能传参数
  4. 静态构造函数不需要访问修饰符,因为是编译器调用,默认为private

值类型与引用类型

值类型:

存储时大小固定,存在栈(连续的空间,因为大小固定所以可以算出连续空间)上

int、char、double、float、long、short、byte、bool、enum、struct、decimal

值类型都是派生自ValueType

值类型不能继承,只能实现接口.

引用类型:

string、数组、类(子定义数据类型)、接口、委托、

int[]n={1,2,3};//引用类型。

引用类型都派生自:Object

引用类型可以继承(类之间可以继承)

枚举

一组和数值有关的常量

enum Gender { Male,Female}

enum Weeks { 星期日=0,星期一,}

把字符串转换成枚举Gender g = (Gender)Enum.Parse(typeof(Gender),”Male”);

结构

就是小类,值类型

不能继承类

可以实现接口

不能有显示无参构造函数(隐式)

结构中不能给字段赋初始值

引用类型定义的都是引用,传递时都是栈上面的地址

按引用传递是传递的栈的地址

按值传递传递的是栈中内容的拷贝

ref关键字可以让值类型也按引用传递


接口

什么是接口?

  • 接口就是一种规范,协议(*),约定好遵守某种规范就可以写通用的代码。
  • 定义了一组具有各种功能的方法。(只是一种能力,没有具体实现)

接口的意义:

  • 接口可以多继承
  • 继承于接口可以表现为一种能力或行为,并不一定需要存在必定的父子联系
  • 值类型可以实现接口,但不能继承类

显示实现接口

显示实现接口后,只能通过接口来调用。

为什么要显示实现接口?

  • 方法重名后的解决办法。

什么是“显示实现接口”?

  • 实现接口中的方法时用:接口名.方法名(),并且没有访问修饰符,默认private

“显示实现接口”后怎么调用?

  • 只能通过接口变量来调用,因为显示实现接口默认为private。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface IFlyable
{
void Fly();
}
public interface IFlyable2
{
void Fly();
}

public class Student : IFlyable, IFlyable2
{
public void Fly()
{
throw new NotImplementedException();
}

void IFlyable2.Fly()
{
Console.WriteLine();
}
}

接口的特点

  1. 接口是一种规范。为了多态。
  2. 接口不能被实例化。
  3. 接口中的成员不能加“访问修饰符”。(默认为public)
  4. 接口中的成员不能有任何实现。
  5. 接口中只能有方法、属性、索引器、事件,不能有“字段”。
  6. 接口与接口之间可以继承,并且可以多继承。
  7. 实现接口的子类必须实现该接口的全部成员。
  8. 一个类可以同时继承一个类并实现多个接口,如果一个子类同时继承了父类A,并实现了接口IA,那么语法上A必须写在IA的前面。class MyClass:A,IA{},因为类是单继承的。
  9. 当一个抽象类实现接口的时候,如果不想把接口中的成员实现,可以把该成员实现为abstract。(抽象类也能实现接口,用abstract标记)
  10. “显示实现接口”,只能通过接口变量来调用(因为显示实现接口后成员为private)。

异常处理

异常处理的一般代码模式:

try{ …可能发生异常的代码… }catch{ …对异常的处理… }finally{…无论是否发生异常、是否捕获异常都会执行的代码… }

try块:可能出问题的代码。当遇到异常时,后续代码不执行。

catch块:对异常的处理。记录日志(log4net),继续向上抛出等操作。(只有发生了异常,才会执行。)

finally块:代码清理、资源释放等。无论是否发生异常都会执行。

异常处理代码的其他几种形式:

  1. try → 多个catch → 一个finally
  2. try→(1个或多个catch),多个catch的顺序问题。可以没有finally。
  3. try→finally(只能有一个),没有catch也可以。

注意点:

  • 发生异常后,try块中,异常代码后的代码不会执行。
  • finally块中的代码,无论是否发生异常都会执行。
  • finally中不能写return语句。
  • try中有return语句,finally也会执行
  • 即便没有catch(或者没有找到合适的catch块),finally中的代码也会执行,但finally之后的代码则不会。(见备注1.)

函数参数前的修饰符

ref 跟out不可用于函数重载

params 可变参数 无论有几个参数,必须出现在参数列表的最后。可以为可变参数直接传递一个对应类型的数组。

ref 是一个栈地址,引用传递,可以把值传递强制改为引用传递

out 让函数可以输出多个值

  • 在方法中必须为out参数赋值
  • out参数的变量在传递之前不需要赋值,即使赋值了也不能在方法中使用。(赋值没意义)

ref

  • 参数在传递之前必须赋值
  • 在方法中可以不为ref参数赋值,可以直接使用。

ref应用场景内部对外部的值进行改变,out则是内部为外部变量赋值,out一般用在函数有多个返回值的场所。


Equals、==、ReferenceEquals

Equals、==可以被子类重写

object.ReferenceEquals唯一可以判断两个对象是否为同一个

1
2
3
4
5
6
7
8
9
10
11
12
13
string str = "luox78";
            string str1 = "luox78";

            Console.WriteLine(str.Equals(str1));//true
            Console.WriteLine(str == str1);//true
            Console.WriteLine(object.ReferenceEquals(str, str1));//true暂存池的原因

            string str = new string(new char[] {'l','u','o'});
            string str1 = new string(new char[] { 'l''u''o' });

            Console.WriteLine(str.Equals(str1));//true
            Console.WriteLine(str == str1);//true
            Console.WriteLine(object.ReferenceEquals(str, str1));//false

String

为什么字符串类不可继承?

子类如果继承字符串类以后可能会对字符串类进行修改,导致改变字符串的特性。其中最重要的特性就是不可变性,string由于被很多类所使用,为了线程安全,所以设计成不可变。

  • 字符串暂存池(拘留池)(针对字符串常量),依赖于字符串的“不可变性”,如果没有不可变性就不可能有“池”

    • 只有声明的时候直接赋值的string才会被放入暂存池
    • 内部维护一个哈希表key为字符串,value是地址。每次为一个新变量赋值都会找key中是否有,如果有则直接把value中的地址赋值给新变量
  • (*)(暂存、拘留、驻留)字符串留用(Intern,针对变量常量,见备注1)。

    • 对于动态字符串本身在哈希表中没有,通过这种Intern可以添加到该哈希表中,目的为了提高性能。

      • String.Intern(xx), Intern 方法使用暂存池来搜索与 str 值相等的字符串。如果存在这样的字符串,则返回暂存池中它的引用。如果不存在,则向暂存池添加对 str 的引用,然后返回该引用。
      • String.IsInterned(xx),此方法在暂存池中查找 str。如果已经将 str 放入暂存池中,则返回对此实例的引用;否则返回 nullNothingnullptrnull 引用

StringBuilder

  • StringBuilder高效的字符串操作

    • 当大量进行字符串操作的时候,比如,很多次的字符串的拼接操作。
    • String 对象是不可变的。 每次使用 System. String 类中的一个方法时,都要在内存中创建一个新的字符串对象,这就需要为该新对象分配新的空间。 在需要对字符串执行重复修改的情况下,与创建新的 String 对象相关的系统开销可能会非常大。 如果要修改字符串而不创建新的对象,则可以使用 System.Text. StringBuilder 类。 例如,当在一个循环中将许多字符串连接在一起时,使用 StringBuilder 类可以提升性能。
  • StringBuilder != String//将StringBuilder转换为String.用ToString();

  • StringBuilder仅仅是拼接字符串的工具,大多数情况下还需要把StringBuilder转换为String.

    • StringBuilder sb = new StringBuilder();
    • sb.Append();//追加字符串
    • sb.ToString();//把StringBuilder转换为字符串。
    • sb.Insert();
    • sb.Replace();

垃圾回收

  • CLR的一个核心功能—垃圾回收。
  • 垃圾回收的目的:提高内存利用率。
  • 垃圾回收器,只回收托管堆中的内存资源,不回收其他资源(数据库连接、文件句柄、网络端口等)。

  • 什么样的对象才会被回收?

    • 没有变量引用的对象。没有变量引用的对象,表示可以被回收了(null),断了线的风筝,再也回不来了。
  • 什么时间回收?

    • 不确定,当程序需要新内存的时候开始执行回收。
    • GC.Collect();//手动调用垃圾回收器。不建议使用,垃圾回收时会暂停一下(非常短暂)让程序自动去GC。
  • 垃圾回收器中“代”的概念:

    • 共3代:第0代、第1代、第2代。
    • 各代的回收频率:第0代最高,其次第1代,再次第2代。也就是说越老的对象生存几率越大。
  • .net中垃圾回收机制:mark-and-compact(标记和压缩),一开始假设所有对象都是垃圾,遍历这些对象,有引用的将标记位标记位有引用,压缩后转到下一代。
  • 除了内存资源外的其他资源怎么办?~或者Dispose(),实现IDisposable接口

    • finalize C#中的叫法
    • 用于非托管资源的资源释放
    • 在GC回收之前会调用它,然后再回收

弱引用

弱引用可以被垃圾回收,只提供没被回收前想要的地址

当创建对象非常耗时时,用弱引用可以节省时间

1
2
3
4
5
6
7
8
9
10
Person p = new Person()
{
Name = "luox78"
};
var wr=new WeakReference(p);
p = null;
if (wr.IsAlive)
{
Console.WriteLine((wr.Target as Person).Name);
}