C#初学笔记

毕业设计要用到c#。
在学过了c++和java后,初看《c#入门经典》和《c#高级编程》,不免会把c#和java与c++在语法方面做些比较。做下笔记。

预编译:
像C++一样,c#中有预编译命令

namespace 和 using:
namespace和java的package相当,不像java那样一个文件中只能有一个public类,命名空间是逻辑组合,不是物理组合,一个文件可以有多个public类,一个类也可以写在多个文件中。namespace可以嵌套;可以给namespace起别名,如using S = System. 

using有点像java中的import。但using只能引用一个命名空间;而java中用“import ...*;”表示引用整个包,也可以只引用包中的一个类。
可以这样得到类所在的命名空间:

MyClass a = new MyClass();
Console.WriteLine(a.GetType().Namespace);

 

基本数据类型:
像c++中一样有unsigned类型,如uint,ushort等等(在不安全代码中还可以进行指针操作),还有一个decimal类型。
string(引用类型)可以像数组那样使用:string s = "haha"; char c = s[0];

匿名类型var:

var s = "string";//在编译的时候根据等号右边的类型转换成string类型
var a = new //等号右边是一个匿名类型
{
    name="hh",
    age = 3,                
};

goto:
java中被废掉的goto关键字又回来了

const 和 readonly:
定义常量用const关键字。类中的const修饰的常量默认就是静态的了(相当于java中的static final);而readonly修饰的变量可以不是静态的,但只能在定义时赋值或构造函数中设置(相当于java中的final)。const常量是在编译时设置,而readonly在运行时设置的。

@:
@符号后面的字符串就是其字面意思(没有转意字符),比如string s = @"\n" 则s的内容就是"\n"而不是一个换行符

foreach:

int[] intArray = new int[3];
foreach(int i in intArray){...}

switch:
可以用于string;default不一定要写在最后一句,但效果和写在最后一样;每个case必须有break或return或goto case ...结束,两个或多个case连续写在一起的情况除外

enum:
默认的underlying type是int,从0开始赋值,可以像int一样进行运算,也可以和int相互转换。也可以underlying type也可以是其他的基本数据类型,也可以自己赋值。

enum Orientation:byte{//指定underlying type为byte
    east,
    west=3,
    north,
    south,
    north,
    unknown=east }
 ...
Orientation o = Orientation.south;            
byte i = (byte)o;
Orientation east = (Orientation)0;
Orientation direction = (Orientation)Enum.Parse(typeof(Orientation), "north");//字符串转化为枚举


值类型struct:
struct派生于System.ValueType,它又派生于System.Object
struct和class很像,同样可以有属性和方法,也可以实现接口,但不能继承。重要的是c#中struct是值类型,而class是引用类型 。(c++中struct翻译为结构体,但是c++中的struct和class都是值类型)

MyStruct struct1 = new MyStruct();
MyStruct struct2 = struct1;
//struct1复制了一份给struct2,对struct2的设置不会影响struct1
MyClass class1 = new MyClass();
MyClass class2 = class1;
//对class2的修改会影响class1,因为它们指向同一个实例


typeof运算符:
typeof(ClassName)感觉像java里面的Object.class,而object.GetType()像java里面的object.getClass(); 

可用(Nullable)修饰符?

int? a = null;//这样值类型和枚举类型就可以被赋值为null了
Console.WriteLine(typeof(int?));//int? 是有别于int 的另一种类型
a = 3;
Console.WriteLine(a.GetType());//奇怪的是被赋值之后a又变回int类型了


ref,out:
ref使得参数按引用传递,学过c++的话对这个不会陌生

void Triple(ref int a)
{a *= 3;}
...
int a = 3;
Triple(ref a);
Console.WriteLine(a);//a变成9

out和ref功能和用法相同,区别在于用ref传入函数的参数必须先初始化,out不用

params:
params关键字使函数可以接受个数任意的参数
int fun(params int[] a){...}就像java中的int fun(int...a){...}

Main函数:
有4种static int Main(string[]args),static void Main(string[]args),static int Main(),static void Main()。Main函数的可见性无所谓


多维数组,变长数组:

int[][] array = new int[3][];// 变长数组            
array[0] = new int[3];
array[0][1] = 43;

int[,] array2 = new int[4, 5];//多维数组
array2[0, 1] = 32;

而java中都是变长数组,即数组的数组

checked和unchecked:
使用checked会在溢出是抛出异常,默认是unchecked

byte b = 255;
checked { b++; }//抛出异常
Console.WriteLine(b);

 

delegate:
委托(代理)好像就是c++中的函数指针,委托也是一种类,派生于System.MulticastDelegate,而System.MulticastDelegate有派生于System.Delegate,MutlicastDelegate是Delegate的链表。

int add(int a,int b){return a+b;}
int subtract(int a,int b){return a-b;}
delegate int Fun(int a,int b);
Fun f = add;
int a = f(1,1);//得到2
f = subtract;
int b = f(1,1);//得到0

Fun g = f + add;
Fun g2 = g;
g2 -= add;//值得注意的是delegate虽是应用类型,但这里对g2的修改并不会影响g,这个和string类型一样的道理

多播委托可以使用+、-、+=、-=运算符

event:
事件处理时同样可以用委托delegate来处理,但加了event关键字后编译器会对委托进一步封装,限制了一些事件发布时逻辑上可能出现的错误
我原来以为事件在概念上很难OOP思想来理解,不如java中监听器的概念清晰;但看了《c#本质论》知道了事件和委托,还有属性被编译器编译后也是用类的方法来实现的

集合:
Array和ArrayList等都用中括号[]来索引,Dictionary还可以用任意类型的引索(这让我想到了js)

Dictionary<string, int> dic = new Dictionary<string, int>();
List<int> l = new List<int>();
...
int a = (int)al[0];
int b = dic["haha"];
//如果[key]找不到对应的value会抛出异常而不是返回null,有一个TryGetValue方法不会抛异常

 

函数重载:
virtual显式指定虚函数或虚属性。不用virtual则默认不是虚拟的(这和C++一样,而java则默认都是虚拟的)。
override关键字用于重载虚函数,new关键字用于显式声明子类的函数隐藏了父类的同名函数。

class A
{
    public void M() { Console.WriteLine("a"); }
    public virtual void V() { Console.WriteLine("a"); }       
}
class B : A
{
    public new void M() { Console.WriteLine("b"); }
    public override void V() { Console.WriteLine("b"); }
}
...
A a = new B();
B b = new B();
a.M();//a
a.V();//b
b.M();//b
b.V();//b

虚函数的调用取决于实例的类型,而非虚函数的调用取决于引用变量的类型
试了一下发现verride不能和virtual一起用,但如果将来有一个C类要重载B类的V()方法,怎么使B.V()成为虚函数啊?


操作符重载:
重载的方法和C++中的一样(记得学C++时我最喜欢这个了,很方便啊),但限制比C++多。比如必须声明为public和static;不能重载“+=”,重载“+”之后“+=”自然可以使用;不能重载“=”;可以重载“&”,不能重载“&&”;“<”和“>”、“<=”和“>=”、“!=”和“=”要成对重载,不能只重载一个;而且好像只能重载成静态static的。

class Time
{
    public int hour, min;
    public Time(int h, int m) { hour = h; min = m; }
    public static Time operator +(Time a, Time b) 
    { return new Time(a.hour + b.hour, a.min + b.min); }
}

 

自定义数据类型转换:
很强大啊,用法像重载操作符一样,同样必须是public static。可以在类和基本数据类型之间,类和类之间转换。implicit指明可以隐式转换,explicit指明必须显示转换。转换函数必须写在源类型或目标类型的类或结构体定义里面,有继承关系的本来就可以转换的类之间不能重载。

class C
{ 
    public int x;
    public C(int i) { x = i; }
    public static implicit operator int(C c) { return c.x; }
    public static explicit operator C(int i) { return new C(i); }
}
...
int i = new C(3);//i=3
C c = (C)5;//c.x=5
byte b = (byte)new C(2);//b=2,C被转换成int类型后被转换成byte

 

boxing和unboxing:
在引用类型和值类型之间相互装换,boxing是自动进行的,而unboxing是要显式说明的

int i = 0;
object o = i;//i被自动装箱成引用类型o
string str = 2.ToString();//2被被自动装箱成一个临时的引用类型
int b = (int)o;//取消装箱
Console.WriteLine(Object.ReferenceEquals(i,i));
//值类型用ReferenceEquals(a,b)函数进行比较总是得到false,因为装箱之后的引用变量指向了原来值类型变量的一份拷贝,而不是原值。这样对装箱对象的操作不会影响原来的值类型的内容。

《C#高级编程》上的意思是:定义结构体时,.Net Framework会隐式的提供一个隐式类,与其结构体相同,却是一个引用类型,用于装箱。枚举类型也有类似情况。

is和as:
“is”和java中的“instanceof”相当;as用于尝试类型转换

ClassA a = (ClassB)b;//无法转换时可能抛出异常
ClassA a = b as ClassA//无法转换时a=null


unsafe
c# 中的基本数据类型可以像C语言那样有指针操作,但这种不安全代码要用unsafe{...}括起来。下面的代码用指针将一个int整型分解成4个字节,再将4个字节合成一个int:

int beforeConvert = 5;
byte[] convertedBytes = new byte[sizeof(int)];
int afterConvert;
unsafe
{
    Console.WriteLine("int " + beforeConvert + " to bytes:");
    byte* p = (byte*)&beforeConvert;
    for (int i = 0; i < sizeof(int); i++)
    {
        convertedBytes[i] = *p++;
        Console.WriteLine("bytes[" + i + "]=" + convertedBytes[i]);
    }
    p = (byte*)&afterConvert;
    for (int i = 0; i < sizeof(int); i++)
    {
        *p++ = convertedBytes[i];
    }
    Console.WriteLine("bytes to int: "+afterConvert);
}


lock:
和java中的"synchronized”相当,感觉还是"lock”的意思比较直白。
lock(object){...}只是一种简便的语法,实际编译后还是用一个Monitor类的Enter()和Exit()方法实现的同步
lock语句不能用值类型变量当做锁,因为值类型会被封箱成引用类型,而引用指向了新的地址,已不是同一个变量,所以不能实现同步。关于boxing和unboxing《c#本质论》讲的比较清楚

关于类的可见性:
protected——只能由类或派生类的访问
internal——只能由定义它的工程(程序集)内部代码访问,可修饰类,类不写public默认就是internal
(java中的protected 成员相当于c#中的protected internal 成员)
private——默认不写就是private
sealed——相当于java中的final,使类不能被派生,或使函数不能被重载
extern——不很清楚,引入dll中的外部函数时有用
static——修饰静态成员;也可以修饰类,一个静态类是只有静态成员的类,不能实例化,比如Math类

property:
使用时让初学者分不清是属性(property)还是字段(field),其实和java中的setter/getter方法一样的功能,还弄了一大堆花括号,真是的...
不过慢慢就会发现属性的方便之处,可以直接像字段那样使用。
接口中不但可以包含方法,也可以包含属性,指定实现接口的类必须提供的字段

PascalCasing:
C#习惯定义变量名的时候头字母大写,有时我都分不清某个函数是静态的还是非静态的,因为调用它的前面那个名字不知是类名还是变量名。感觉还是camelCasing比较可爱

继承:
同样是单继承、多接口,继承的类写在第一个,然后是要实现的接口,用逗号隔开。父类用base关键字而不是super,构造函数后面可用冒号跟其他的构造函数

interface I { void DoSth();}
class A
{
    private int a;
    public A(int a) { this.a = a; }
    public void Method() { Console.WriteLine("A.Method()"); }
}
class B : A ,I
{
    private int b;
    public void DoSth(){}
    public new void Method() { base.Method(); Console.WriteLine("B.Method"); }
    public B(int a, int b) : base(a) { this.b = b; }
    public B(int b):this(0,b){}
}


跨线程调用窗体控件:
这在java中是允许的,尽管这么做不是线程安全的;但是在c#中默认是会出异常的。比如:

private void button1_Click(object sender, EventArgs e)
{
    Thread t= new Thread(() =>
        { 
            int i = 0;
            while (i++ < 10)
            {
                textBox1.AppendText("i");//抛出异常,因为textBox1是在UI线程中生成的,默认不能在其他线程中被调用
                Thread.Sleep(1000);
            }
        });
    t.IsBackground = true;
    t.Start();
}

解决办法一种是让程序允许扩线程调用控件,在窗体的构造函数中加入一句允许跨线程,但这样做仍然不是线程安全的:

public Form1()
{
    InitializeComponent();
    Form.CheckForIllegalCrossThreadCalls = false;//加入这句
}

另一种方法比较正统:
private void button1_Click(object sender, EventArgs e)
{
    Thread t= new Thread(() =>
        { 
            int i=0;
            while (i++ < 10)
            {
                Append();
                Thread.Sleep(1000);
            }
        });
    t.IsBackground = true;
    t.Start();
}
void Append()
{ 
    if (textBox1.InvokeRequired)
    {
        textBox1.Invoke(new Action(Append));
    }
    else
    {
        textBox1.AppendText("i");
    }
}



异常处理:
java强制要处理异常或抛给调用函数,而C#中抛出异常的函数不必非要用try/catch 处理也不会报错(是不是这样可以提高性能?),但使得初学者不知道某个函数可能抛出什么异常,也弄不清什么情况下要处理异常。
在捕获异常,做一些适当处理后可以用“throw;”语句再次抛出异常;这与“throw new Exception(...)”重新引发异常的区别是:后者将栈追踪重置为重新引发的位置,而前置栈追踪位置是原始引发位置 


推荐一本书《c#本质论》,它写得比较深,书中告诉读者C#语言上的一些简化被编译器翻译成CIL语言(Common Intermediate Language,相当于java中的运行于虚拟机的字节码)后是怎么用类来实现的

你可能感兴趣的:(入门,C#,基础知识)