Fork me on GitHub

The C# Programming Language(Third Edition) Part I

看了3章了,发现有些东西不记一下,可能就瞬间就忘了。当做个电子笔记吧~

 

属性是字段的一种自然延伸。与字段不同的是,属性不代表存储位置,它只是提供了访问机制,当它们的值被读写时可以执行特定的语句。 

 

任何枚举类型的默认值都是由整型值0转换而来。所以:

Color c =0 ;    //okay

 

“在决定把一个internal类型的成员声明为public还是internal的时候,如果稍后这个类型被提升为public,是不是希望这个成员也变成public?如果是,一开始就可以将成员声明为public”

 

class A

{

    int x;

    static void F(B b){

        b.x=1;            //okay

   }

}

class B:A

{

    static void F(B b){

        b.x=1;            //error,x not accessible

    }

}

B类从A类里继承了私有成员x。不过因为这个成员是私有的,所以它只能在A的类主题里访问到。

 

public class A

{

        protected int x;

        static void F(A a,B b){

               a.x = 1;        //okay

               b.x = 1;       //okay

         }

}

public class B:A

{

        static void F(A a,B b){

                a.x = 1;    //error,must access through instance of B

                b.x = 1;    //okay

        }

}

在A里,通过A和B的实例都可以访问到x,因为不管在哪种情况下,访问都是经由A的实例或则是A子类的实例发生的。但是B里的情况不同,这里通过实例A是无法访问到x的,因为A并非继承自B。

 

class A{...}

public class B:A{...}

B类会导致编译期错误,因为A没有达到B的可访问性。

class A{...}

public class B

{

    A F(){...}

    internal A G(){...}

    public A H(){...}

}

B中的H方法会导致编译期错误,因为返回类型A没有达到这个方法的可访问性。

 

readonly修饰符表示那些字段是只读字段。只有在字段声明阶段或者是在这个类的构造函数里才允许对readonly的字段进行赋值。

"readonly保护的是字段的位置(而非那个位置上的值)不会在构造函数之外被改变。”

public class Names
{

     public static readonly StringBuilder FirstBorn = new StringBuilder("Joe");

     public static readonly StringBuilder SecondBorn = new StringBuilder("Sue");

}

在构造函数之外,直接修改FirstBorn实例导致编译错误:

Names.FirstBorn = new StringBuilder("Biff");  //Compile error

但是修改StringBuilder实例却完全没有问题:

Names.FirstBorn.Remove(0,6).Append("Biff"); //okay

 

 方法在形式参数的类型里出现的任何类型参数都不是根据它的名字来识别,而是根据在方法的类型参数列表中的序号位置来识别的。

void F(string a,int b) //okay

void F(int b,string a) //okay

void F(int x) //F(int)

void F(ref int x) //F(ref int)

void F(out int x) //F(out int) error

void F(string[] a) //F(string[])

void F(params string[] a) //F(string[]) error

任何ref和out参数修饰符都是签名的一部分。但是ref和out却不能再同一个类型里声明,因为它们的签名仅仅依靠ref和out来区分。

返回类型和params修饰符也不属于签名的一部分。

 

”可被销毁“和”可被回收“之间的区别很重要。A的析构函数已经先执行了,但是A的方法还是有可能被另一个析构函数调用。同时,运行对象的析构函数可能让一个队形在程序的主线中再次变得可用。

 

整型字量的类型规则:

没有后缀---int,uint,long,ulong.

后缀为U或u---uint,ulong.

后缀为L或l---long,ulong.

后缀为UL,Ul,uL,LU,Lu,lU或lu---ulong.

 

实数字量规则:

没有后缀,默认为double。

后缀为F或f,为float类型。

后缀为D或d,为double类型。

后缀为M或m,为decimal类型。

 

位操作符是对数据按二进制位进行运算的操作符。c#位操作符包括按位与(&) | ~ << >>
int a=6&3;
Console.WriteLine("a={0}",a);
//6的二进制是00000110,3的二进制是00000011,按位与后等于00000010,即2
int b=6|3;
Console.WriteLine("b={0}",b);
//6的二进制是00000110,3的二进制是00000011,按位或后等于00000111,即7
int c=~6;
Console.WriteLine("c={0}",c);
//6的二进制是00000110,按位取反后是11111001即-7
int d=6^3;
Console.WriteLine("d={0}",d);
//6的二进制是00000110,3的二进制是00000011,按位异或后等于00000101,即5
int e=6<<3;
Console.WriteLine("e={0}",e);
//6的二进制是00000110,左移三位后等于00101000,即48
int f=6>>2;
Console.WriteLine("f={0}",f);
//6的二进制是00000110,右移二位等于00000001,即1

 

带类型参数的显示转换:

class X<T>

{

      public static long F(T t){

            return  (long)(object)t;   //okay,but will only work when T is long

     }

       // 进一步扩展:

      static T Cast<T>(object value){  return (T)value;  }

}

//只有当类型在编译期就已知为数字的时候才会考虑标准数字转换

X<int>.F(7)  //error 装箱的int不能直接转换为long

string s = Cast<string>("s");    //okay - reference conversion

int i = Cast<int>(3);    //okay - unboxing

long l = Cast<long>(3);    // invalidCastException:attempts an unboxing

                                      //  instead of an int->long numeric conversion

 

抱怨>:<转换实在太麻烦,太繁琐了,好多没看懂。

匿名函数转换

泛型委托类型Func<A,R>,以表示一个接受类型A的参数并返回类型R的值的函数:

delegate R Func<A,R>(A arg);

在下面的赋值中,

Func<int,int> f1 = x => x + 1;  //okay

Func<int,double> f2 = x => x+1; //okay

Func<double,int> f3 = x => x+1;  //error

每个匿名函数的参数和返回类型都是由赋值给匿名函数的变量的类型决定的。

几个不同特点的匿名函数和C#构造代码的相应翻译。

public delegate void D();    
没有捕捉任何外部变量的匿名函数:    
static void F(){
     D d = () => { Console.WriteLine("test"); };

}

翻译:

static void F(){

      D d = new D(_Method1);

}

static void _Method1(){

      Console.WriteLine("test");

}   

引用了this的实例成员:

int x;

void F(){
      D d = () => { Console.WriteLine(x); };

}

翻译:

int x;

void F(){

     D d = new D(_Method1);

}

void _Method1(){

      Console.WriteLine(x);

}

捕捉局部变量:

void F(){
      int y = 123;

      D d = () => { Console.WriteLine(y); };

}

局部变量的生命周期就必须延长到至少和匿名函数委托的生命周期一样。

翻译:

void F(){

      __Locals1  __locals1 = new __Locals1();

      __locals1.y = 123;

      D d = new D(__locals1.__Method1);

}

class __Locals1

{

      public int y;

      public void __Method1(){

               Console.WriteLine(y);

      }

}

 

表达式里操作符的计算顺序是由操作符的优先级和结合性决定的。表达式里的操作数按照从左至右的顺序进行计算。例如在F(i)+G(i++)*H(i)中,方法F调用时使用的是i的旧值,然后方法G调用时也是使用i的旧值,最后方法H调用时使用的是i的新值。

从高到低所有操作符的优先级:

1 基本 (x)     x.y        f(x)      a[x]       x++       x―― new      typeof     sizeof       checked     unchecked 
2 单目 +    -    ! ~ ++x      ――x      (T)x
3 乘法与除法 *       /       %
4 加法与减法 +        -
5 移位运算 ≤      ≥
6 关系运算 ﹤       >       <=       >=    is
7 条件等 = =        ! =
8 位逻辑与 &
9 位逻辑异或 ^
10 位逻辑或 |
11 条件与 &&
12 条件或 ‖
13 条件 ?:
14 赋值 =       *=       /=      %=     +=    -=     <<=    >>=     &=   ^=     |=

赋值操作符合条件操作符(?:) 是右结合的,就是说操作符是从右向左进行的。例如,x=y=z 会作为x= (y=z)来计算。

在x+y*z的列子里,x会率先被求职,接着是y,然后才是z。不能因为乘法是“发生”在加法之前的,所以就认为y和z是在x之前求职的。

可重载的一元操作符:

+  - ! ~ ++ -- true fasl

可重载的二元操作符:

+ - * / % & | ^ << >> == != > < >= <=

 

数字提升:

byte a = 1, b = 2;

byte c =  a + b ; //compile-time error

在这种情况下,变量a和b都被提升为int,所以

byte c = (byte)(a + b) ;

 

简单名字:简单名字由一个标示符构成,有时候还会跟一个类型参数列表。

“任何会导致一个简单名字的语义发生变化的重构都会被编译期捕捉到。”

class Test
{

      double x;

      void F(bool b){

           x = 1.0;

           if(b){

               int x;

               x = 1;

            }

       }

}

会导致编译期错误,因为x在外层块里指向了不同的实体。

class Test

{

      double x;

      void F(bool b){

            if(b){

                x = 1.0;

             }

             else{

                 int x;

                 x = 1;

              }

      }

}

是合法的,因为x在外层块里从来没有被使用过。

 

成员访问:

在E.I形式的成员访问里,如果E是一个标识符,且E作为简单名字的含义是一个常量、字段、属性、局部变量或者是和作为类型名的E的含义拥有相同类型的参数的话,那么E的这两种可能的含义都是合法的。(例如访问E的静态成员和嵌套类型)

struct Color

{

      public static readonly Color White = new Color(...);

      public static readonly Color Black = new Color(...);

      public Color Complement(){...};

}

class A

{

      public Color Color;

      void F(){

            Color = Color.Black;

            Color = Color.Complement();

       }

       static void G(){

             Color c = Color.Write;

       }

}

 

简单名字和成员访问的产生式可以在表达式的文法里制造歧义。例如:

F(G<A,B>(7));

可以解释为两个参数G<A 和 B>(7)的调用F。或者,它也可以解释为对泛型方法G的调用F。

 

调用表达式:

实例方法比扩展方法的优先级更高,在内层命名空间声明里的扩展方法比外层命名空间声明里的扩展方法优先级更高,直接在一个命名空间里声明的扩展方法比用using命名空间指令导入到同一个命名空间里的扩展方法优先级更高。例如,

public static class E

{

      public static void F(this object obj,int i){ }

      public static void F(this object obj,string s){ }

}

class A{}

class B

{

      public void F(int i){ }

}

class C
{

      public void F(object obj){ }

}

class X

{

      static void Test(A a,B b,C c){

            a.F(1);               //E.F(object , int)

            a.F("hello");        //E.F(object ,string)

            b.F(1);              //B.F(int)

            b.F("hello");       //B.F(object , string)

            c.F(1);              //C.F(object);

            c.F("hello");       //C.F(object);  

}

 

this访问:

this访问只允许出现在实例构造函数、实例方法或者实例访问器的块里。

base访问:

base访问用来访问在当前类或结构里被同名成员隐藏的基类成员。这类访问只能出现在实例构造函数、实例方法或者实例访问器的块中。

后缀递增和递减操作符:

class A{

      public static A operator++(A x){

             return new A();

      }

}

class B:A{

      static void Main(){

           B x = new B();

           x++;   //error cs0266

      }

}

x++或x--的值是操作之前x的值,而++x或--x的值是操作之后保存在x里的值。

new操作符:

new操作符可以用来创建类型的新实例。new创建值类型时,不会进行动态内存分配。

对象创建表达式的类型必须是类类型、值类型或类型参数。但是它不可以是abstract类类型。

对象初始化语句指定了一个对象的零到多个字段或属性的值。

public class Point

{

      public int X { get; set; }

      public int Y { get; set; }

}

Point a = new Point { X = 0, Y = 1};

集合初始化语句指定了集合的元素。

List<int> digits = new List<int> {0,1,2,3,4,5,6,7,8,9};

集合对象必须是实现了System.Collections.IEnumerable的类型。

数组创建表达式可以用来创建数组类型的新实例。

new int [,]  {{0,1},{2,3},{4,5}} 同 new int[3,2] {{0,1},{2,3},{4,5}}

数组创建表达式允许用数组类型的元素实例化一个数组,但是这样数组的元素必须是手动初始化的。例如,

int [][] a = new int[100][];

for(int i = 0;i<100;i++) a[i] = new int[5];

委托创建表达式可用于创建委托类型的新实例。委托创建表达式的参数必须是方法组、匿名函数或委托类型的值。

匿名对象创建表达式可用于创建匿名类型的对象。匿名对象初始化语句声明了一个匿名类型,并返回一个那个类型的实例。

var p1 = new {Name = "Lawnmower", Price = 495.00 };

var p2 = new {Name = "Shovel", Price = 26.95 };

p1 = p2 ; //okay

 

typeof 操作符

typeof操作符可用来获取一个类型的System.Type对象。

typeof(type)

typeof(unbound-type-name)

typeof(void)

 

checked和unchecked操作符

checked和unchecked操作符可在整数类型的算术操作和转换时控制溢出检查上下文。

 

默认值表达式

默认值表达式可用来获取类型的默认值。

default(type)

 

转换表达式

转换表达式可用于显式地将一个表达式转换为给定的类型。

 

按位求补操作符

对已~x形式的操作,操作结果都是对x按位求补。 

 

位移操作符

<<和>>操作符可用于进行移位操作。

对于x<<count或x>>count形式的操作,<<操作符将x向左移位,超出x结果范围的高位字节将会被丢弃,而低位空出来的字节位会被设为零。

>>操作符将x向右移位,当x是int或long时,x的低位字节将会被丢弃,剩下的字节位向右移,如果x是非负数,那么高位空出来的字节位就设为零,若x为负数,那么就设为1.当x是uint或ulong时,x的低位字节会被丢弃,剩下的字节位向右移,且高位空出来的字节位会被设为零。

 

is操作符

is操作符可用来动态地检查一个对象的运行时类型是不是和给定的类型兼容。

as操作符

as操作符可用来显示地将一个值转换为给定的引用类型或可空值类型。如果转换无法完成,结果为null。

if(x is Foo){ (Foo)x).DoFoo(); }

不如

Foo foo = x as Foo;

if( foo!=null) { foo.DoFoo(); }

因为is、as和类型转换在.net的底层操作里其实就是一回事,所以只进行一次操作,看结果是不是null,然后继续非null结果的操作比进行操作要好。

 

整数逻辑操作

&操作符将对两个操作数进行按位逻辑与,|操作符将对两个操作数进行按位逻辑或,而^操作符将会对两个操作数进行按位逻辑异或。

 

条件逻辑操作符

&&和||操作符被称为条件操作符。也称作“短路”逻辑操作符。

x&&y操作符和X&y操作符相对应,不过y只有在x不为false的时候才会计算。

x||y操作和x|y操作相对应,不过y只有在x不为true的时候才会计算。

 

Null拼接操作符

??操作符被称为null拼接操作符。

a??b形式的null拼接操作表达式要求a是一个可空值类型或引用类型。如果a非null,那么a??b的结果就是a;否则,其结果为b。操作只有在a为null的情况下才会计算b。

 

条件操作符

?:操作符被称为条件操作符。

b?x:y形式的条件表达式会首先计算条件b。然后,如果b为真,那么计算x并将其结果作为操作的结果。否则,计算y并将其结果作为操作的结果。永远不会同时计算x和y。

条件操作符是右结合的,例如a?b:c?d:e形式表达式会被当成a?b:(c?d:e)来计算。

 

匿名函数表达式

匿名函数是一个表示了“内联的“方法定义的表达式。匿名函数自身并不含有任何值,但是它可以转换为兼容的委托或表达式树类型。

=>操作符的优先级和赋值操作符是一样的,并且它是右结合的操作符。

匿名函数的例子,

x=>x+1   // implicitly typed,expression body

x=>{return x+1; } //implicitly typed,statement body

(int x) => x+1  //explicitly typed,expression body

(int x) => {return x+1;} //explicitly typed,expression body

(x,y) => x * y  //multiple parameters

() => Console.WriteLine() //no paramters

delegate (int x) {return x+1;} //anonymous method expression

delegate {return 1+1; }  //parameter list omitted

 

外部变量

任何作用域包含了lambda表达式或匿名表达式的局部变量、值参数或参数数组,它们都被称为匿名函数的外部变量。在一个类实例函数成员里,this值被认为是值参数,并且是任何包含在函数成员里的匿名函数的外部变量。

当匿名函数引用一个外部变量时,可以说这个外部变量被匿名函数捕捉了。被捕捉的外部变量的生命期被延长。

 

?看不懂的局部变量实例化- -||

using System;

delegate void D();

class Test

{

static D[] F(){

      D[] result = new D[3];

      int x;

      for(int i=0;i<3;i++){

            x = i * 2 + 1;

            result[i] = () => {Console.WriteLine(x);};

      }

      return result;

}

static void Main(){

       foreach(D d in F()) d();

}

 输出全为:5

 

查询表达式

查询表达式提供了一种集成式语言查询语法,这种语法是与sql和xquery等关系与层次查询语言相似的语法。

查询表达式从from子句开始,到select或group子句结束。打头的from子句后面可以跟零到多个from、let、where、join或orderby子句。每个from子句都是一个引用了范围变量的生成器,这里的范围是序列的元素。每个let子句则引入一个表示通过计算前面范围变量所得得值的范围变量。每个where子句都是一个过滤器,负责从结果里排除掉一些项。每个join子句会用原序列里的特定的键去和另一个序列里的键相比较,产生配对。每个orderby子句会根据指定的条件重新排序结果。而最后的select或group子句则指定了结果在范围变量里的形态。最后,into子句可以通过将一个查询的结果作为子查询里的生成器来”拼接“多个查询。

查询表达式翻译成一系列的方法调用:

Where,Select,SelectMany,Join,GroupJoin,OrderBy,OrderByDescending,ThenBy,ThenByDescending,GroupBy,Cast.

?= =||又是一大堆不懂得,查询表达式。

 

透明标识符

特定的翻译会将*代表的透明标识符注入范围变量里。

 

组合赋值

byte b=0;

char ch='\0';

int i = 0;

b+=1;   //okay

b+=1000;   //error,b = 1000 not permitted

b+=i;   //error,b = i not permitted

b+=(byte)i;  //okay

ch+=1;   //error,ch=1 not permitted

ch+=(char)1;  //okay

 

常量表达式

常量表达式是一个可以在编译期就完整计算出来的表达式。

诸如装箱、拆箱和非null值的隐式转换都不允许出现在常量表达式。

 

Extern 别名

extern别名可以为一个命名空间引入一个作为别名的标识符。被指定的别名命名空间是在程序的源代码之外的,同时它还应用于别名命名空间的嵌套命名空间。

extern alias X;

extern alias Y;

class Test

{

   X::N.A a;

   X::N.B b1;

   X::N.B b2;

   X::N.C c;

}

 

using 指令

using别名指定会为命名空间或类型引入一个新的别名。

using命名空间指令会导入一个命名空间的类型成员。

 

类型声明

类型声明可以是类声明、结构声明、接口声明、枚举声明或者委托声明。

 

posted @ 2010-02-21 22:14  idoku  阅读(255)  评论(0编辑  收藏  举报