《C#图解教程》读书笔记

第5章——方法

var关键字

能够从初始化语句的右边推断出类型,e.g.

var total = 15;
var mec = new MyExcellentClass();

使用var的一些条件

  • 只能用于本地变量,不能用于成员变量;
  • 只能在变量声明中包含初始化时使用;
  • 一旦编译器推断出变量的类型,它就是固定且不能更改的;

嵌套块中的变量

不管嵌套级别如何,都不能在第一个名称的有效范围内声明另一个同名的本地变量。

本地常量

  • 在声明时必须初始化;
  • 声明后不能改变;
  • 初始化值必须在编译期决定;
  • 可以是null,但不能是对象的引用;

值参数

  • 在栈中为形参分配空间;
  • 将实参的值复制给形参;

引用参数

  • 必须在方法的声明和调用中都使用ref修饰符;
  • 实参必须是变量,在用作实参前必须被赋值;
  • 不会为形参在栈上分配内存;

输出参数

  • 必须在声明和调用中都使用out修饰符;
  • 实参必须是变量;
  • 在方法内部, 输出参数在能够被读取之前必须被赋值,所以没必要在方法调用之前为实参赋值;
  • 在方法返回之前,方法内部贯彻的任何可能路径都必须为所有输出参数进行一次赋值;

参数数组

  • 在一个参数列表中只能有一个参数数组;
  • 如果有,它必须是列表中的最后一个;
  • 由参数列表表示的所有参数都必须具有相同的类型;
  • 声明时在数据类型前使用params修饰符;
  • 声明时在数据类型后放置一组空的方括号;void listInts(params int[] inVals)
  • 调用时不需要修饰符;
  • 如果没有对应的实参,编译器会创建一个有零个元素的数组;

命名参数

  • 调用时,·形参的名字后面跟着冒号和实际的参数值或表达式;c.Calc(c:2, a:4, b:3);
  • 如果同时使用位置参数和命名参数,所有位置参数必须先列出;

默认参数

  • 只要值类型的默认值在编译的时候可以确定,就可以使用值类型作为默认参数;
  • 只有在默认值是null的时候,引用类型才可以作为默认参数使用;
  • 所有必填参数必须在默认参数声明前声明,如果有params参数,必须在所有默认参数之后声明;
  • 如果需要随意省略默认参数列表中的默认参数,而不是从列表的最后开始,那么必须使用默认参数的名字(命名参数)来消除赋值的歧义;

第6章——深入理解类

成员常量

  • 没有自己的存储位置,只是在编译时被编译器替换;
  • 不能声明为static;
  • 即使没有类实例也可以通过类访问,并且对类的每个实例都是可见的;

属性

public int myValue
{
    set
    {
        SetAccessCode
    }
    get
    {
        GetAccessCode
    }
}

与成员变量类似:

  • 是命名的类成员;
  • 有类型;
  • 可以被赋值和读取;

与成员变量有区别:

  • 不为数据存储分配内存;
  • 执行代码;
  • set访问器为属性赋值;
  • get访问器为属性获取值;

set访问器总是:

  • 拥有一个独立的,隐式的值参,名称为value,与属性的类型相同;
  • 拥有一个返回类型void;

get访问器总是:

  • 没有参数;
  • 拥有一个跟属性类型相同的返回类型;

只读和只写属性:

  • 只有get访问器的属性称为只读属性;
  • 只有set访问器的属性称为只写属性;
  • 两个访问器至少有一个必须定义;

自动实现属性:

  • 不声明后备字段,编译器根据属性的类型分配存储;
  • 不能提供访问器的方法体,必须被简单地声明为分号。get担当简单的内存读,set担当简单的内存写;
  • 除非通过访问器,否则不能访问后备字段。必须同时提供读写访问器;
class C1
{
    public int MyValue
    {
        set;get;
    }
}

静态属性:

  • 不能访问类的实例成员,但可以被实例成员访问;
  • 不管类是否有实例,它们都是存在的;
  • 当从类的外部访问时,必须使用类名引用,而不是类实例;

静态构造函数

  • 初始化静态成员变量;
  • 只能有一个静态构造函数,而且不能带参数;
  • 不能有访问修饰符;
  • 不能显示调用,系统会自动调用,在类的任何实例被创建之前,在类的任何静态成员被引用之前;

对象初始化语句

new TypeName          {FieldOrProp=InitExpr, FieldOrProp=InitExpr,...}
new TypeName(ArgList) {FieldOrProp=InitExpr, FieldOrProp=InitExpr,...}

允许你在创建新的对象实例时,设置字段和属性的值。
- 初始化的字段和属性必须是public;
- 初始化发生在构造函数执行之后;

readonly修饰符

成员变量可以用readonly修饰符声明,其作用类似于const,一旦被设定就不能改变。

  • 可以在下列任意位置设置它的值;

    • 字段声明语句;
    • 类的任何构造函数,如果是static字段,必须在静态函数中设置;
  • readonly字段的值可以在运行时决定;

  • 可以是实例字段,也可以是静态字段;
  • 在内存中有存储位置;

索引器

string this [int index, ...]
{
    set
    {
        SetAccessCode
    }
    get
    {
        GetAccessCode
    }
}
  • 不用分配内存来存储;
  • 可以只有访问器,也可以两个都有;
  • 索引器总是实例成员,因此不能声明为static
  • 可以重载,参数列表必须不同。

访问器的访问修饰符

class Person
{
    public string name {get; private set;}
}
  • 仅当既有get访问器又有set访问器时,其访问器才可以有修饰符;
  • 虽然两个访问器都必须出现,但只能有一个有访问修饰符;
  • 访问器的修饰符必须比成员的访问级别有更严格的限制性;

分部类

  • 每个分部类声明必须标为partial class
  • 每个分部类的声明都含有一些类成员的声明;
  • 类的分部类声明可以在同一个文件中也可以在不同文件中;

分部方法

  • 返回类型必须是void
  • 不能包含访问修饰符,所以分部方法是private的;
  • 参数列表不能包含out参数;
  • 定义声明和实现声明中都必须包含partial修饰符,直接放在关键字void之前;
  • 可以只有定义部分而没有实现部分。这种情况下,编译器把方法的声明和方法内部任何对方法的调用都移除。不能只有分部方法的实现部分而没有定义部分。

第7章——类和继承

所有类都继承自object类

  • 一个类的基类只能有一个,即单继承;

屏蔽基类的成员

  • 要屏蔽数据成员,需要声明一个新的相同类型的成员,并使用相同的名称;
  • 要屏蔽函数成员,需要声明新的带有相同签名的函数成员,签名由名称和参数列表组成,不包括返回类型;
  • 要让编译器知道你在故意屏蔽继承的成员,要使用new修饰符,否则会报警告;
  • 也可以屏蔽静态成员;

基类访问

  • 基类访问表达式由关键字base后面跟着一个点和成员的名称组成;base.Field1

虚函数和覆写方法

  • 派生类的方法和基类的方法有相同的签名和返回类型;
  • 基类的方法使用virtual标注;
  • 派生类的方法使用override标注;
  • 覆写和被覆写的方法必须有相同的可访问性;
  • 不能覆写static和非虚函数;
  • 方法,属性和索引器,以及事件都可以被声明为virtualoverride
  • 当基类引用调用覆写的方法时,调用被沿着派生层次上溯执行,一直到标记为override的方法的最高派生版本;
  • 如果在更高的派生级别有该方法的其他声明,但没有被标记为override,那么它们不会被调用;

构造函数执行顺序

Created with Raphaël 2.1.2初始化成员基类构造函数自身构造函数

构造函数初始化语句

  • 使用关键字base指明使用哪个基类的构造函数; public MyDerivedClass(int x, string y):base(x, y)
  • 使用关键字this指明使用当前类的哪个构造函数;public MyClass(int x):this(x, "using default string")

类访问修饰符

  • 标记为public的类可以被系统内任何程序集中的代码访问;
  • 标记为internal的类只能被它自己所在的程序集内的类看到,这是默认的可访问级别;

成员访问修饰符

  • public
  • private,能够被嵌套在它的类中的类成员访问
  • protected
  • internal
  • protected internal,对所有继承该类的类以及所有程序集内部的类可见,是protectedinternal的并集

抽象类

  • 不能创建抽象类的实例;
  • 抽象类使用abstract修饰符声明;
  • 抽象类可以包含抽象成员或普通成员;
  • 抽象类自己可以派生自另一个抽象类;
  • 任何派生自抽象类的类必须使用override关键字实现该类的所有抽象成员,除非派生类自己也是抽象类;

抽象成员

  • 必须是函数成员;
  • 必须用abstract修饰符标记;
  • 不能有实现代码;
  • 只能在抽象类中声明;
  • 只有四个类型的成员可以声明为抽象:
    • 方法
    • 属性
    • 事件
    • 索引
  • 不能附加virtual修饰符;
  • 派生类中抽象成员的实现必须指定override修饰符;
abstract public void PrintStuff(string s);
abstract public int MyProperty
{
    get;
    set;
}

密封类

  • 只用用作独立的类,不能被用作基类;
  • sealed修饰符标注;

静态类

  • static修饰符标注;
  • 所有成员必须是静态的;
  • 可以有一个静态构造函数,但不能有实例构造函数,不能创建该类的实例;
  • 静态类是隐式密封的,也就是说,不能继承静态类;

扩展方法

  • 声明扩展方法的类必须声明为static
  • 扩展方法本身必须声明为static
  • 扩展方法必须包含关键字this作为它的第一个参数类型,并在后面跟着它所扩展的类的名称;
namespace ExtensiionMethods
{
    sealed class MyData
    {
        private double D1, D2, D3;
        public double Sum(){return D1+D2+D3;}
    }
    static class ExtendMyData
    {
        public static double Average(this MyData md)
        {
            return md.Sum() / 3;
        }
    }
    class Program
    {
        static void Main()
        {
            MyData md = new MyData();
            md.Average();
        }
    }
}

第8章——表达式和运算符

用户定义的类型转换

publicstatic修饰符是必须的

  • 隐式转换

    public static implicit operator TargetType (SourceType Identifier)
    {
        ...
        return ObjectOfTargetType;
    }
  • 显示转换

    public static explicit operator TargetType (SourceType Identifier)
    {
        ...
        return ObjectOfTargetType;
    }

运算符重载

publicstatic修饰符是必须的

public static LimitedInt operator +(LimitedInt x, double y)

typeof运算符

System.Type t = typeof(SomeClass);

第9章——语句

do循环

do
    ...
while(...);

using语句

using管理的资源必须实现System.IDisposable接口

using (ResourceType Id1 = Expr1, Id2 = Expr2, ...) EmbeddedStatement

第10章——结构

结构与类的区别:

  • 类是引用类型而结构是值类型;
  • 结构是隐式密封的,所以是不能被派生;
  • 结构可以有实例构造函数和静态构造函数,但不允许有析构函数;
  • 编译器隐式地为每个结构提供一个无参数的构造函数,而且不能被删除或重定义,而类只有在没有其他构造函数声明时才会有默认构造函数;
  • 字段初始化语句是不允许的;
  • 所有结构都派生自System.ValueType;

第11章——枚举

设置底层类型

enum TrafficLight : ulong
{
    ...
}

位标志

enum CardDeckSettings : uint
{
    SingleDeck    = 0x01, //位0
    LargePictures = 0x02, //位1
    FancyNumbers  = 0x04, //位2
    Animation     = 0x08  //位3
}

CardDeckSettings ops = CardDeckSettings.SingleDeck
                     | CardDeckSettings.FancyNumbers
                     | CardDeckSettings.Animation;

//判断标志字是否包含特定的位标志集
bool useFancyNumbers = ops.HasFlag(CardDeckSettings.FancyNumbers);

第12章——数组

数组

  • 数组一旦创建,大小就固定了,C#不支持动态数组;
  • 有两种类型多维数组:
    • 矩形数组:某个维度的所有子数组有相同长度的多维数组;不管有多少维度,总是使用一组方括号int x = myArray[4,6,1];
    • 交错数组:每个子数组都是独立数组;可以有不同长度的子数组;为数组的每个维度使用一对方括号myArray[2][7][4]
  • 数组是对象9(引用类型),数组实例是从System.Array继承的对象;

一维数组和矩形数组的声明

int[] array1; //一维数组
int[,] array2; //二维数组
int[,,] array3; //三维数组
  • 可以使用任意多的秩说明符(逗号);
  • 不能设置数组各个维度长度;
  • 数组声明后,维度数就固定了,维度长度要等到实例化才能确定;

一维数组和矩形数组的实例化

int arr1 = new int[5];
int[,,] arr2 = new int[3,6,2];

一维数组和矩形数组的显示初始化

不用输入维度长度,编译器可以通过初始化值的个数来推断长度

int[,] arr = new int[,]{{1,2},{3,4},{5,6}};

交错数组

  • 声明时每个维度都有一对独立的方括号;
  • 不能在声明时初始化顶层数组之外的数组int[][] arr = new int[3][4]//error
  • 每个子数组要单独创建

    int[][] arr = new int[3][];
    arr[0] = new int[] {1,2,3};
    arr[1] = new int[] {1,2};
    arr[2] = new int[] {1,2,3,4,5};
  • 子数组可以是矩形数组

    int [][,] arr = new int[3][,];
    arr[0] = new int[,]{{1,2},{3,4}};   
    ...

第13章——委托

delegate void MyDel(int value); //声明委托类型
class Program
{
    void PrintLow(int value){...}
    void PrintHigh(int value){...}
    static void Main()
    {
        Program program = new Program();
        MyDel del; //声明委托变量
        del = new MyDel(program.PrintLow);
        //del = program.PrintLow; 另一种简洁写法
        del(10); //执行委托
    }
}

组合委托

MyDel delA = myInstObj.Fun;
MyDel delB = SClass.Fun;
MyDel delC = delA + delB;
delA += delB;

从委托移除方法

delA -= SClass.Fun;
  • 如果在调用列表中的方法有多个实例,-=运算符将从列表最后开始搜索,并且移除第一个与方法匹配的实例;
  • 试图删除委托中不存在的方法没有效果;
  • 试图调用空委托会抛出异常。可以通过把委托与null进行比较判断委托的调用列表是否为空。如果调用列表为空,则委托是null

调用带有返回值的委托

  • 调用列表中最后一个方法返回的值就是委托调用返回的值;
  • 调用列表中所有其他方法的返回值都会被忽略;

调用带引用参数的委托

  • 在调用委托列表中的下一个方法时,参数的新值会被传给下一个方法;

匿名方法

class Program
{
    delegate int Del(int InParam);
    static void main()
    {
        Del del = delegate(int x)
                          {
                              return x + 20;
                          }
    }
}
  • 当满足以下两个条件时可以省略匿名方法的参数列表:
    • 委托的参数列表不包含任何out参数;
    • 匿名方法不适用任何参数
  • 如果委托类型声明指定最后一个参数为params类型的参数,匿名方法参数列表忽略params关键字;
  • 匿名方法可以访问它们外围作用域的局部变量和环境;

第14章——事件

例子:

delegate void Handler(); //声明委托

class Increment
{
    public event Handler CountedADozen; //创建事件并发布
    public void DoCount()
    {
        for(int i = 0; i < 100; ++i)
            if(i % 12 == 0 && CountedADozen != null)
                CountedADozen(); //触发事件
    }
}

class Dozens
{
    public int DozensCount {get; private set;}
    public Dozens(Incrementer incrementer)
    {
        DozensCount = 0;
        incrementer.CountedADozen += IncrementDozensCount; //订阅事件
    }

    //事件处理程序
    void IncrementDozensCount()
    {
        DozensCount++;
    }
}

class Program
{
    static void Main()
    {
        Incrementer incrementer = new Incrementer();
        Dozens dozensCounter = new Dozens(incrementer);

        incrementer.Docount();
    }
}

移除事件

  • 跟委托移除方法一样,用-=运算符;
  • 如果一个处理程序向事件注册了多次,当移除处理程序时,只会移除列表中该处理程序的最后一个实例;

第15章——接口

声明接口

  • 接口声明不能包含以下成员:
    • 数据成员
    • 静态成员
  • 只能包含以下类型的非静态成员函数的声明:
    • 方法
    • 属性
    • 事件
    • 索引器
  • 函数成员的声明不能包含任何实现代码;
  • 接口名称习惯都以大写的I开始;
  • 支持分部接口声明;
  • 接口成员时隐式public的,不允许有任何访问修饰符;
  • 只有类和结构才可以实现接口;
  • 如果类从基类继承并实现了接口,基类列表中的基类名称必须放在所有接口之前;
  • 可以实现任意数量的接口;
  • 如果一个类实现了多个接口,其中一些接口有相同签名和返回类型的成员,那么类可以实现单个成员来满足所有包含重复成员的接口;
  • 支持继承;

as运算符

  • 如果类实现了接口,表达式返回指向接口的引用;
  • 如果类没有实现接口,表达式返回null而不抛异常;

显示接口成员实现

class MyClass: IIfc1, IIfc2
{
    void IIfc1.PrintOut(string s) {...}
    void IIfc2.PrintOut(string s) {...}
}
  • 只能通过指向接口的引用来访问,其他的类成员都不可以直接访问它们;

转换

溢出检测上下文

checked(表达式);
unchecked(表达式);
checked
{
    ...
}
unchecked
{
    ...
}
  • 如果在转换时会产生溢出则抛出一个OverflowException异常;

装箱转换

  • 装箱是一种隐式转换,它接受值类型的值,根据这个值在堆上创建一个完整的引用类型对象并返回对象引用;

拆箱转换

  • 拆箱是显示转换;

is运算符

Expr is TargetType

如果Expr可以通过以下方式成功转换为目标类型,运算符返回true
- 引用转换
- 装箱转换
- 拆箱转换

as运算符

  • 类似于强制转换运算符,只是不抛出异常,如果转换失败,返回null;
  • 转换的目标类型必须是引用类型;
  • 只能用于引用转换和装箱转换;

第17章——泛型

类型参数的约束

where TypeParam: constraint, constraint, ...

e.g.
class MyClass<T1, T2, T3>
                where T2:Customer
                where T3:IComparable
{
    ...
}

共有五种类型的约束:

  • 类名:只有这个类型的类或从它继承的类才能用作类型实参;
  • class:任何引用类型,包含类,数组,委托和接口都可以用作类型实参;
  • struct:任何值类型都可以用作类型实参;
  • 接口名:只有这个接口或实现这个接口的类型才可以用作类型实参;
  • new():任何带有无参数公告构造函数的类型都可以用作类型实参;这叫做构造函数约束;

where子句可以以任何次序列出,然而,where子句种的约束必须有特定的顺序

  • 最多只能有一个主约束,如果有必须放在第一位;
  • 可以有任意多的接口名约束;
  • 如果存在构造函数约束,则必须放在最后;

泛型方法

public void PrintData<S, T>(S p, T t) where S:person
{
    ...
}

第18章——枚举器和迭代器

枚举器和可枚举类型

  • 获取一个对象枚举器的方法是调用对象的GetEnumerator方法。实现GetEnumerator方法的类型叫做可枚举类型;
  • foreach会调用GeetEnumerator方法获取对象的枚举器,从枚举器中请求每一项并且把它作为迭代变量,代码可以读取该变量但不可以改变;
  • IEnumerator接口包含三个函数成员:Current,MoveNext,Reset;
  • IEnumerable接口包含一个函数成员:GetEnumerator。它返回对象的枚举器;

迭代器

//产生枚举器的迭代器
public IEnumerator<string> IteratorMethod()
{
    ...
    yield return ...;
}
//产生可枚举类型的迭代器
public IEnumerable<string> IteratorMethod()
{
    ...
    yield return ...;
}

第21章——命名空间和程序集

using指令

  • 必须放在源文件的顶端,在任何类型声明之前;
  • 它们应该于当前源文件中的所有命名空间;
  • 可以给命名空间或命名空间内的类型起别名;

    using Syst = System;
    using SC = System.Console;

第23章——预处理指令

  • 预处理指令必须和C#代码在不同的行;
  • 不需要以分号结尾;
  • 每行必须以#字符开始;
  • 允许行尾注释;
  • 预处理指令所在的行不允许分隔符注释;
  • #define和#undef只能用在源文件的第一行,在C#代码开始后就不能再使用;

区域指令

  • 被放置再希望标注的代码段之上;
  • 用指令后的可选字符串文本作为其名字;
  • 在之后的代码中必须由#endregion指令终止;
  • 可以嵌套;
  • 用visual studio允许隐藏或显示区域;

pragma warning指令

#pragma warning disable 615,414 //关闭部分警告
#pragma warning restore 618 //重新开启部分警告

#pragma warning disable //关闭所有警告
#pragma warning restore //重新开启所有警告

第24章——反射和特性

Obsolete特性

class Program
{
    //[Obsolete("Use method SuperPrintOut", true)] //true表示被标记为错误而不仅是警告
    [Obsolete("Use method SuperPrintOut")]
    static void PrintOut(string str)
    {
        ...
    }
    static void Main(string[] args)
    {
        PrintOut("xxx");
    }
}

Conditional特性

#define DoTrace
usig System;
class Program
{
    [Conditional("DoTrace")]
    static void TraceMessage(string str)
    {
        Console.WriteLine(str);
    }
    static void Main()
    {
        TraceMessage("Start of Main");
        Console.WriteLine("Doing work in Main");
        TraceMessage("End of Main");
    }
}

如果DoTrace被定义,那么编译器会包含所有对TraceMessage的调用代码;否则,不会调用。

调用者信息特性

using System;
using System.Runtime.CompilerServices;
public static class Program
{
    public static void MyTrace(string message, 
                                [CallerFilePath] strig filename = "",
                                [CallerLineNumber] int lineNumber = 0,
                                [CallerMemberName] string callingMember = "")
    {
        ...
    }
    public static void Main()
    {
        MyTrace("Simple message");
    }
}

DebuggerStepThrough特性

  • 告诉编译器在执行代码时不要进入该方法调试;
  • 位于Sytem.Diagnostics空间;
  • 可用于类,结构,构造函数,方法或访问器;

多个特性

  • 多个特性可以以下面两种形式列出:
    • 分成两个独立的特性片段相互叠在一起;
    • 单个特性片段,特性之间用逗号分隔;
  • 可以以任何次序;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章