1.转移序列 @ 的用法:
"C://TEMP//Mydir//Myfile.doc"
应用@后:
@"C:/TEMP/Mydir/Myfile.doc"
唯一的例外就是 " ,必须用/"指定。
2.在switch中,C#只能执行一个case语句。不能像C++那样,在没遇到break之前,可以一次执行多个。
3.溢出检查环境:
checked(expression) 检查是否溢出
desVar = checked((byte)sourceVar);
unchecked(expression) 不检查是否溢出
或者在VS中右击Solution explorer窗口中的项目,选择 properties,点击左边的build。
点击advanced, 勾择 check for arithmetic overflow/underflow。默认是不检查的。
4.Convert.ToInt16(val);将val转换为INT16类型。
Convert.*在System名称空间中,不是C#本身。并且他们总是进行溢出检查,所以Checked和unchecked关键字没用。
5.枚举类型转换
枚举值转成字符串:ToString()
字符串转成枚举值:(enumerationType)Enum.Parse(typeof(enumerationType), enumerationValueString);
string myString = "north";
orientation myDirection = (orientation)Enum.Parse(typeof(orientation), myString);
6.数组的声明
int[] val = {1,2,3,4};
int[,] val = {{1,2,3,4},{4,5,6,7},{5,6,7,1}}; 声明了一个三行四列的二维数组。
int[,,]声明三维数组。另一种声明方式:int[,,] val =new int[2,2,4];
多维数组,可称为矩形数组,因为每一行的元素个数都相同。
数组的数组,变长数组。
int[][] = {new int[] {1},
new int[] {1,2},
new int[] {1,3,4}};
7.字符串处理
string类型的变量可以看作是Char变量的“只读”数组,如下:
string mystring = "A string";
char myChar = myString[1];
获得一个可写char数组的方法,如下:
string myString = "A string";
char[] myChars = myString.ToCharArray();
«string».ToLower() & «string».ToUpper() 用来转换大小写。
«string».Trim() & «string».TrimStart() & «string».TrimEnd()用来消除字符串中的空格。
«string».PadLeft()&«string».PadRight():
myString = "Aligned";
myString = myString.PadLeft(10); 在左边加3个空格,凑成10个字符
myString = myString.padLeft(10,'-');在左边加3个“-”,凑成10个字符
Split()方法
string myString = "This is a test";
char[] separator = {' '}; 如果有多个条件可以再次输入,如果遇到“a”分开,可以char[] separator = {' ','a'};
string[] myWords;
myWords = myString.Split(seperator);
foreach(string word in myWords)
{
Console.WriteLine("{0}",word);
}
Console.ReadKey();
运行结果:
This
is
a
test
得到的每个单词没有空格。在使用split时,删除了分隔符。
8.C#参数数组,用params关键字定义
如果有多个参数,这个参数必须是函数定义中的最后一个参数。实例如下:
static int SumVals(params int[] vals)
{
int sum = 0;
foreach(int val in vals)
{
sum+=val;
}
return sum;
}
调用方式:SumVals(1,5,2,9);
9.ref & out作函数参数。如:foo(ref int i) & foo(out int i)
ref 使用前“必须赋值”,离开函数时,可以修改也可以不修改。所以,ref 的参数不能声明为const。
在调用函数是时也要加ref,如:foo(ref myNumber); ref是函数签名的一部分。
out 使用前不用赋值(赋值也没用,会被清空),离开函数时,必须被重新赋值。否则,会报错。
调用方法参考ref
10. 委托 delegate
委托使用三部曲:
1. 声明
delegate int ProcessDelegate(int param1, int param2);
2. 委托变量
ProcessDelegate Process;
3. 绑定
mutiply(int i, int j);
Process = new ProcessDelegate(mutiply);
4. 调用
int result = Process(2,3);
11. IDisposable接口
用于控制释放所示用的资源。
支持IDisposable接口的对象必须实现其Dispose()方法。
使用using关键字可以优化使用这个方法的结构,如:
ClassName VariableName = new ClassName();
....
using(VariableName)
{
......
}
或者
using(ClassName VariableName = new ClassName())
{
......
}
这样,在using代码块中使用VariableName,并在代码块末尾自动删除(调用Dispose())。
12. public pravite protected
protected派生类可以访问,外部代码无法访问。
13. 虚拟、抽象、重写 (virtual & abstract & override «& extern» )
·i 抽象类可以有非抽象成员。并且抽象类的派生类可以为抽象类,且不用实现所有的抽象成员。非抽象派生类必须实现所有的抽象成员。
·ii 隐藏 与 重写
重写虚方法的时候,会覆盖基类的原方法
public class MyBaseClass
{
public virtual void DoSomething()
{ //Base Implementation.
Console.WriteLine("Base imp");
}
}
public class MyDerivedClass:MyBaseClass
{
public override void DoSomething()
{ //Derived class implementation
Console.WriteLine("Derived imp");
}
}
当 MyDerivedClass myObj = new MyDerivedClass();
MyBaseClass myBaseObj;
MyBaseObj = myObj;
myBaseObj.DoSomething();
结果如下:
Derived imp
--------------------------------------------
隐藏基类方法时,仍然可以通过基类访问。也就是说,基类的方法被覆盖了。
可以用new关键字显示声明。也可以不用,隐式声明隐藏,但会显示warning。
public class MyBaseClass
{
public virtual void DoSomething()
{ //Base Implementation.
Console.WriteLine("Base imp");
}
}
public class MyDerivedClass:MyBaseClass
{
new public void DoSomething()
{
Console.WriteLine("Derived imp");
}
}
结果如下:
Base imp
---------------------------------------------
在派生类中,调用重写或隐藏的基类方法,base关键字。
public class MyBaseClass
{
public virtual void DoSomething()
{
// Base implementation
}
}
public class MyDerivedClass:MyBaseClass
{
public override void DoSomething()
{
// Derived class implementation, extends base class implementation.
base.DoSomethig();
// More derived class implementation
}
}
注意:base使用的是对象实例,所以不能再静态成员中使用。
this 关键字
用法同上,该关键字引用对象实例。
14. 对象的包含关系
用一个成员字段包含对象实例,就可以实现包含关系。
15. 创建事件(简单的例子)
MnewButton.Text = "New Button"; // 初始化属性
newButton.Click += new EventHandler(newButton_Click); // 绑定事件处理程序
......
private void newButton_Click(object sender, System.EventArgs e){....;};
16. internal 声明的类只能在当前项目中使用。P166
17. 构造函数的执行序列
·i public class MyBaseClass
{
public MyBaseClass(){}
public MyBaseClass(int i){}
}
-----------------
public class MyDerivedClass: MyBaseClass
{
public MyDerivedClass()
public MyDerivedClass(int i)
public MyDerivedClass(int i, int j)
}
-------------------
当MyDerivedClass myObj = new MyDerivedClass()时,System.Object.object()-»MyBaseClass.MyBaseClass()-»MyDerivedClass.MyDerivedClass()
当MyDerivedClass myObj = new MyDerivedClass(4)时,System.Object.object()-»MyBaseClass.MyBaseClass()-»MyDerivedClass.MyDerivedClass(4)
当MyDerivedClass myObj = new MyDerivedClass(4,8)时,System.Object.object()-»MyBaseClass.MyBaseClass()-»MyDerivedClass.MyDerivedClass(4,8)
·ii base关键字,如果
public class MyDerivedClass: MyBaseClass
{
........
public MyDerivedClass(int i, int j):base(i)
}
那么,当MyDerivedClass myObj = new MyDerivedClass(4,8)时,System.Object.object()-»MyBaseClass.MyBaseClass(4)-»MyDerivedClass.MyDerivedClass(4,8)
public class MyDerivedClass: MyBaseClass
{
........
public MyDerivedClass():base(5)
}
那么,当MyDerivedClass myObj = new MyDerivedClass()时,System.Object.object()-»MyBaseClass.MyBaseClass(i)-»MyDerivedClass.MyDerivedClass(4,8)
·iii this关键字,构造函数初始化器
public class MyDerivedClass: MyBaseClass
{
public MyDerivedClass():this(5,6)
........
public MyDerivedClass(int i, int j):base(i)
}
当MyDerivedClass myObj = new MyDerivedClass()时, System.Object.Object() -» MyBaseClass.MyBaseClass(int i) -» MyDerivedClass.MyDerivedClass(int i ,int j) -» MyDerivedClass.MyDerivedClass()
18. 类库项目
类库项目没有程序入口点,所以,可以建一个新的应用程序(比如控制台程序),在引用(References)中添加引用(添加编译的类库项目-*.DLL)。
19. 接口
在实现接口的类中,可以用关键字virtual 或 abstract来执行接口成员,但不能使用static 或 const。
另外,接口成员还可以在基类中实现,如下:
public interface IMyInterface
{
void DoSomething();
void DoSomethingElse();
}
public class MyBaseClass
{
public void DoSomething()
{ }
}
public class MyDerivedClass:MyBaseClass,IMyInterface
{
public void DoSomething()
{}
}
------------------------------------------
基类实现的接口,意味着派生类隐式的支持这个接口,如下:
public interface IMyInterface
{
void DoSomething();
void DoSomethingElse();
}
public MyBaseClass:IMyInterface
{
public virtual void DoSomething() {}
}
public class MyDerivedClass:MyBaseClass
{
public override void DoSomething() {} //隐式支持基类继承的接口
}
如果派生类是隐藏 而不是 重写 接口, 则方法IMyInterface.DoSomething()总是引用基类版本,即使派生类用接口访问也是调用基类方法。
---------------------------------------------------------
显示 及 隐式 执行 接口成员
public class MyClass: IMyInterface
{
void IMyInterface.DoSomething() {} // 显示执行
public void DoSomethingElse() {} // 隐式执行
}
显示执行:
MyClass myObj = new MyClass();
IMyInterface myInt = myObj;
myInt.DoSomething();
隐式执行:
MyClass myObj = new MyClass();
myObj.DoSomethingElse();
只有隐式执行的DoSomethingElse()才可以用MyClass的对象实例直接访问
-------------------------------------------------------
如果接口中有属性(有get和set访问器),在实现接口的类中,可以添加访问器,但添加的访问器的修饰符(如,public,protected...),必须比接口中定义的更加严格才可以添加。
public interface IMyInterface
{
int MyIntProperty
{
get;
}
}
public class MyBaseClass: IMyInterface
{
protected int myInt;
public int MyInterProperty
{
get
{
return myInt;
}
protected set // 相比接口中多了set访问器,但访问修饰符为protected,比get的更加严格
{
myInt = value;
}
}
}
20. partial 关键字
部分类定义:
public partial class MyClass: IMyInterface1
{
.....
}
public partial class MyClass: IMyInterface2
{
.....
}
以上两个和在一起等价于:
public class MyClass:IMyInterface1, IMyInterface2
{
......
}
-----------------------------------
部分方法定义:
部分方法可以是static的,但总是private的,且 不能有返回值。使用的参数不能是out参数,可以是ref参数。不能使用virtual,abstract,override,new,sealed 和 extern。
执行编译时,如果代码包含一个没有执行代码的“部分方法”,会完全删除该方法,并且会删除对该方法的全部引用。
public partial class MyClass
{
partial void MyPartialMethod(); //有partial关键字
}
public partial class MyClass
{
partial void MyPartialMethod() //有partial关键字
{
// method implementation
}
}
21. System.Collections名称空间中的几个接口
· IEnumerable 可以迭代集合中的项,该接口的唯一的方法为 GetEnumerable()可以迭代集合中的各项。实现该接口的类才能用foreach进行迭代。MSDN:http://msdn.microsoft.com/zh-cn/library/system.collections.ienumerable(VS.80).aspx
· ICollection (继承于IEnumerable)可以获取集合中项的个数,并能把项复制到一个简单的数组类型中
· IList(继承于IEnumerable 和 ICollection)提供了几个的项列表,并可以访问这些项,以及其他一些与项列表相关的功能
· IDictionary(继承于IEnumerable 和 ICollection)类似于IList,但提供了可以通过“键码值”而不是“索引”访问的项列表
System.Array类实现了IList、ICollection和 IEnumerable,但不支持IList的一些更高级的功能。它表示大小固定的项列表
System.ArrayList类实现了IList、ICollection和 IEnumerable,可以表示大小可变的项列表。
Arraylist集合可以用AddRange()添加好几个项。这个方法接受带有ICollection接口的任何对象。但该方法不是任何借口的一部分,是ArrayList专有的。
IList的IndexOf()返回找到对象的索引;如果某对象在列表中出现多次,则 IndexOf 方法将始终返回找到的第一个实例。
22. 定义集合-应用System.Collections.CollectionBase抽象类
CollectionBase类有接口 IEnumerable, ICollection 和 IList,但只提供了一些要求的代码。
CollectionBase提供了两个受保护的属性 List 和 InnerList,List可以通过IList接口访问项,InnerList则是用于存储项的ArrayList对象。
例1.
public class Animals : CollectionBase
{
public void Add(Animal newAnimal)
{
List.Add(newAnimal); // 利用List属性操作集合中的项
}
public void Remove(Animal oldAnimal)
{
List.Remove(oldAnimal); // 利用List属性操作集合中的项
}
public Animals()
{}
}
结论:IList的Add() 和 Remove()方法为强类型话的方法。在本例中只能处理Animal类 或 派生于Animal的类。而前面介绍的ArrayList执行代码可以处理任何对象。
例2.
Animals animalCollection = new Animals();
animalCollection.Add(new Cow("Sarah"));
foreach(Animal in animalCollection)
{
........
}
结论:CollectionBase类可以对派生类的集合使用foreach语法
---------------------------------------------
CollectionBase的索引符
public class Animals: CollectionBase
{
....
public Animal this[int animalIndex]
{
get
{
return (Animal)List[animalIndex]; // 因为IList.List属性返回的是一个System.Object对象,所以需要进行显示类型转换
}
set
{
List[animalIndex] = value; // List使用索引符,在IList上,访问CollectionBase中的ArrayList.
}
}
}
然后就可以使用:
animalCollection[0].Feed()
否则,只能用 ((Animal)animalCollection[0]).Feed();
------------------------------------------------------------
23. 定义关键字集合--应用System.Collections.DictionaryBase抽象基类
DictionaryBase实现IEnumerable 和ICollection接口。 DictionaryBase简化了IDictionary接口的实现。
例1:
public class Animals: DictionaryBase
{
public void Add(string newID, Animal newAnimal)
{
Dictionary.Add(newID, newAnimal); //利用Dictionary访问集合项
}
public void Remove(string animalID)
{
Dictionary.Remove(newID);
}
public Animals() {}
public Animal this[string animalID]
{
get
{
return (Animal)Dictionary(animalID);
}
set
{
Dictionary[animalID] = value;
}
}
}
--------------------
foreach工作方式
DictionaryBase需要用 System.Collections.DictionaryEntry结构. 要得到DictionaryBase派生类对象本身(比如,Animal对象本身),就必须使用这个结构的Value成员,也可以使用Key成员得到相关的关键字。
foreach (DictionaryEntry myEntry in animalCollection) //animalCollection是 Dictionary派生类的对象
{
Console.WriteLine("{0} AND {1}", myEntry.Value.ToString(),((Animal)myEntry.Value).Name); //也可以用迭代器实现如下的代码,具体见下一条目。
}
想等价的代码:
foreach (Animal myAnimal in animalCollection) // animalCollection是CollectionBase派生类的对象
{
Console.WriteLine("{0} AND {1}", myAnimal.ToString(),myAnimal.Name());
}
24. 迭代器
·如果要迭代一个类,可使用GetEnumerator(),其返回类型是IEnumerable。
·如果要迭代一个类成员,例如一个方法,则使用IEnumerable。
迭代类成员(方法):
public static IEnumerable SimpleList()
{
yield return "string 1";
yield return "string 2";
yield return "string 3";
}
public static void Main(string[] args)
{
foreach (string item in SimpleList())
Console.WriteLine(item);
Console.ReadyKey();
}
运行结果为:
string 1
string 2
string 3
可以用" yield break; " 中断信息返回
----------------------------
迭代类:
public class Primes
{
private long min;
private long max;
public Primes() : this(2,100) {...}
public Primes(long minimum, long maximum) {...}
public IEnumerator GetEnumerator()
{ ....
if(...)
yield return possiblePrime;
}
}
static void Main(string[] args)
{
Primes primesFrom2To1000 = new Primes(2,1000);
foreach (long i in primesFrom2To1000) // 因为类Primes实现了GetEnumerator方法,所以可以迭代。
{ Console.Write("{0}",i); }
Console.ReadyKey();
}
-------------------------------------------------
迭代器和DictionaryBase集合
public class Animals: DictionaryBase
{
public void Add(string newID, Animal newAnimal) {Dictionary.Add(newID, newAnimal);}
public void Remove(string animalID) {Dictionary.Remove(animalID);}
public Animals() {...}
public Animal this[string animalID]
{
get
{
return (Animal)Dictionary[animalID];
}
set
{
Dictionary[animalID] = value;
}
}
// 添加迭代器
public new IEnumerator GetEnumerator()
{
foreach (object animal in Dictonary.Values)
yield return (Animal)animal;
}
}
接下来就可以在迭代集合中使用Animal对象了
foreach(Animal myAnimal in animalCollection)
Console.WriteLine("{0} AND {1}", myAnimal.ToString(),myAnimal.Name);
25. 浅度复制 与 深度复制
·i 浅度复制
利用受保护(protected)的方法 System.Object.MemberwiseClone(),无参数,返回对象类型。
例:
public class Cloner
{
public int Val;
public Cloner(int newVal) { Val = newVal;} // 构造函数
public object GetCopy()
{
return MemberwiseClone();// MemberwiseClone() 对当前对象执行浅度复制。
}
}
局限:当类中有引用类型时,浅度复制得到的对象,所以用的部分与源对象相同。
·ii 深度复制
类要求实现接口 ICloneable,该接口有一个Clone()方法,无参数,返回对象类型。
public class Content
{
public int Val;
}
public class Cloner: ICloneable
{
public Content MyContent = new Content();
public Cloner(int newval) { MyContent.Val = newVal; }
public object Clone() //实现Clone方法,实现深度复制
{
Cloner clonedCloner = new Cloner(MyContent.Val);
return clonerCloner;
}
}
注意:深度复制的代码( Clone() )是要自己编写的。
Clone()是一个递归过程:
上例中Content类中没有引用类型,所以,可只对Cloner()深度复制。
如果,Content中有引用类型就需要如下代码:
public object Clone()
{
Cloner clonedCloner = new Cloner();
clonedCloner.MyContent = MyContent.Clone();
return clonedCloner;
}
26. 封箱 & 拆箱
封箱(boxing), 值类型 -» System.Object 类型 || 转换为值类型实现的接口类型
拆箱(unboxing),相反过程。
struct MyStruct
{ public int Val; }
MyStruct valType1 = new MyStruct();
object refType = valType1; // 封箱
......
MyStruct valType2 = (MyStruct)refType; // 拆箱
----------------
Struct Mystruct: IMyInterface
{......}
Mystruct valType2 = new MyStruct();
IMyInterface refType = valType2; // 封箱到接口对象中
由以上,可知,封箱是隐式的(不需要显示转换),而拆箱是显示的(需要显示转换)
27. is 运算符
«operand» is «type»
·如果 «type» 是一个类, «operand» 也是该类型,或继承了该类,或封封箱到该类型, 结果为TRUE。
·如果 «type» 是一个接口,«operand» 也是该类型,或实现了该接口,结果为TRUE。
·如果 «type» 是一个值类型,«operand»也是该类型,或被拆箱到该类型中,结果为TRUE。
28. 运算符重载
public class ClassA
{
public int Val;
public static ClassA operator +(ClassA op1, ClassA op2) 运算符重载声明格式,如果是一元运算符,那就只有一个参数。
{
ClassA returnVal = new ClassA();
returnVal.Val = op1.Val + op2.Val;
return returnVal;
}
}
ClassA op1,op2,op3; ......
op3 = op1 + op2;
----------------------------------
混合类型操作符:
......
public static ClassC operator +(ClassA op1, ClassB op2 )
{
ClassC returnVal = new ClassC();
returnVal.Val = op1.Val + op2.Val;
return returnVal;
}
......
混合类型操作符重载注意:
不要把 签名相同的运算符 添加到 多个类中。因为程序不知道该调用哪个运算符。
比如:如果ClassB也定义了上面的操作符,代码就会失败。
另外,操作数的顺序 和 运算符重载的 顺序 要 相同。
ClassA op1 = new ClassA();
ClassB op2 = new ClassB();
ClassC op3 = op2 + op1; // 该代码会出错,因为重载运算符中定义的是 (ClassA op1, ClassB op2), 先ClassA 后 ClassB的顺序。
----------------------------------
可以重载的运算符:
·一元: +, -, !, ++, --, true, false
·二元: +, -, *, /, %, &, |, ^, ««, »»
·比较: ==, !=, «, », «=, »=
"+=" 不能重载,但重载 "+" 相当于重载 "+="
"«" 和 "»" 以及 "«=" 和 "»=" 需要成对重载
重载true 和 false后,就可以在布尔表达式中使用类,如 if(op1) {....}。
重载"==" 和 "!="时,最好也重写Object.Equals() 和 Object.GetHashCode()。
public override bool Equals(Object op1) {......} //注意,参数是Object类型
public override int GetHashCode() {......}
29. IComparable 和 IComparer 接口
可以使用这两个接口对 集合排序。
·IComparable 在要比较的对象的类中实现,提供了CompareTo(..)方法,可比较该对象,和另一个对象。
·ICompare 在一个单独的类中实现,提供了Compare()方法,可比较任意两个对象。
一般,用IComparable给出类的默认比较代码,用实现ICompare的其他类给出非默认比较代码。
类Compare在System.Collections名称空间中,提供了ICompare接口的默认实现方式。可以使用Compare.Default静态成员,接着使用Compare()方法。比如,Compare.Default.Compare(«T» x, «T» y)。
Compare类的注意事项:
·检查传给Compare()的对象是否支持 IComparable。如果支持,就是用对象类的代码。
·null 小于 其他对象。
·字符串根据当前“特定区域性的信息”(非托管代码称为“区域设置”)处理。要使用不同的“区域信息”,Compare类必须实例化,以便传送System.Globalization.CultureInfo对象。
·字符串处理时区分大小写。要不区分大小写,需要用CaseInsensitiveComparer类。
System.Collections名称空间中的一些类没有排序方法,比如,CollectionBase类。 而ArrayList的Sort()方法,只有在ArrayList中填充简单类型时,才能进行排序。对于自己定义的类,必须在类中实现IComparable或是创建一个类实现ICompare类,来进行比较。
下面,分别用 IComparable 和 ICompare来实现自定义类型的排序:
class Person: IComparable
{
public string Name;
public int Age;
public Person(string name, int age) {.....}
public CompareTo(Object obj) // 当Person的ArrayList进行Sort()排序时,会调用该函数实现排序功能。
{
if(obj is Person)
{
Person otherPerson = obj as Person;
return this.Age - otherPerson.Age;
}
else
{
throw new ArgumentException( "......" )
}
}
}
-----------
public class PersonCompareName: ICompare
{
public static ICompare Default = new PersonCompareName();
public int Compare(Object x, Object y)
{
if(x is Person && y is Person)
{
return Compare.Default.Compare(
((Person)x).Name, ((Person)y).Name );
else
throw new ArgumentException(".......");
}
}
}
-----------
static void Main()
{
ArrayList list = new ArrayList();
list.Add(new Person("Jim",30));
.....
list.Add(new Person("Bob",22));
list.Sort(); // 因Person类实现了IComparable接口,所以,Sort()调用CompareTo(Object obj)方法进行排序。
list.Sort(PersonCompareName.Default); // 调用其他实现ICompare的类(PersonCompareName),调用Compare()方法进行排序。
}
30. 转换
· 重载转换运算符
在不相关的类型之间转换时,需要定义 显示 或 隐式 转换:
public class ConvClass1
{
public int val;
public static implicit operator ConvClass2(ConvClass1) // 定义 ConvClass1到ConvClass2的隐士转换
{
ConvClass2 returnVal = new ConvClass2();
returnVal.val = op1.val;
return returnVal;
}
}
public class ConvClass2
{
public double val;
public static explicit operator ConvClass1(ConvClass2 op1)
{
ConvClass1 returnVal = new ConvClass1();
checked (returnVal.val) = (int)op1.val;
return returnVal;
}
}
ConvClass1 op1 = new ConvClass1();
ConvClass2 op2 = new ConvClass2();
.....
op2 = op1; //根据定义,可以隐士转换
op1 = (ConvClass1)op2; //根据定义,需要显示转换
---------------------------------------
· as 转换为指定的引用类型
«operand» as «type»
·«operand» 的类型是«type»类型
·«operand» 可以隐式转换为«type»类型
·«operand» 可以封箱到«type»类型中
如果不能转换,则表达式返回 null。返回null而不是异常,比较好处理。
31. 可空类型 System.Nullable«T»
·声明可空的值类型。
System.Nullable«int» nullableInt;
nullableInt = null; //因为int类型为值类型,如果nullableInt不是可空类型,那么它不能用null赋值。
.......
if (nullableInt == null) {......}//可以像测试引用类型一样,测试可空类型,看它是否为null
另外,还可以使用HasValue属性:
if( nullableInt.HasValue ) {.......} //引用类型不能用HasValue属性,会抛出异常。因为,引用类型的null,表示没有对象,当然就不能通过对象访问。
------------------
另一种,声明可空类型的语法:
int? nullableInt; // int?是System.Nullable«int» 的缩写。
·运算符 和 可空类型
int? op1 = 5;
int? result = op1*2; // 可以运算
******
int result = op1*2; // 不能编译
******
int result = (int) op1*2; //可以编译并运算,但当op1为null时,会抛出System.InvalidOperationException异常。
当可空类型进行运算时,如果有操作数为null, 除bool?之外的简单可控类型,运算结果是null。解释为不能运算。
对于bool?, 如果不需要值为null的操作数就能算出结果,那么就可以运算;如果需要,结果就为null。比如:true|null 结果为true(只要有true,结果就为ture), false|null结果为null(第一个操作数为false,所以需要另一个值来运算,所以结果为null)。
----------------------
·??运算符
op1??op2;
等效于:
op1==null ? op2:op1;
32. 泛型 System.Collections.Generic
这个名称空间中包含处理集合的泛型类型。
·List«T»
成员: Count, Add(T item), AddRange(IEnumerable«t»), AsReadOnly(), Capacity, Clear(), Contains(T item), CopyTo(T[] array, int index), GetEnumerator(), IndexOf(T item), Insert(int index, T item), Remove(T item), RemoveAt(int index), Item....
例:
Animals animalCollection = new Animals(); //Animals继承了CollectionBase。
可替换为:
List«Animal» animalCollection = new List«Animal»();
另一种修改方法为:
public class Animals : List«Animal» {.......} //该方法,可以在集合类Animals中添加额外的成员。
CollectionBase类的用途:1. 向后兼容。2.更多的控制向类的“用户”展示的成员。比如,将集合的Add()方法,使用内部访问修饰符。
-------------------------
·对泛型列表 的 排序 和 搜索
对List«T»排序的两个方法:
1. 在需要的类上提供IComparable«T» 或 提供 ICompare«T»接口
2. 提供泛型委托作为排序方法
排序 和 搜索 的泛型委托:
·Comparison«T» : 这个委托类型用于排序方法,器返回类型 和 参数 是 int method (T objectA, T objectB).
·Predicate«T» : 这个委托类型用于搜索方法,其返回类型 和 参数是 bool method(T targetObject).
例:
public class Vectors : List«Vector»
{
public Vectors() {....}
public Vectors(IEnumerable«Vectors» initialItems)
{
foreach (Vector vector in initialItem)
{
Add(vector);
}
}
......
}
添加一个新类VectorDelegates,用于提供泛型委托所用的方法
public static class VctorDelegates
{
public static int Compare(Vector x, Vector y) //排序方法
{
if(x.R » y.R)
{
return 1;
}
else if(x.R « y.R )
{
return -1;
}
return 0;
}
public static bool TopRightQuadrant(Vector target) //用于搜索
{
if(target.Theta =» 0.0 && target.Theta«= 90.0)
{
return true;
}
else
{
return false;
}
}
}
在主程序中 应用 排序 和 搜索 方法:
static void Main(string[] args)
{
Vectors route = new Vectors();
route.Add(new Vector(2.0, 90.0));
.....
Comparison«Vector» sorter = new Comparison«Vector»(VectorDelegates.Compare); //绑定委托
route.Sort(sorter); //完成排序
.....
Predicate«Vector» searcher = new Predicate«Vector»(VectorDelegates.TopRightQuadrant); //绑定委托
Vectors topRightQuadrantRoute = new Vectors(route.FindAll(searcher)); // 完成搜索
.....
}
33. Dictionary«K,V»
·可以使用Keys 和 Values 属性迭代集合中的 键和值。
Dictionary«string ,int» thing = new Dictionary«string, int»();
foreach (string key in thing.Keys) //用Keys属性迭代
{......}
foreach (string value in thing.Values) //用Values属性迭代
{......}
---------------
可以迭代集合中的项,用KeyValuePair«K,V»实例来获取(与DictionaryEntry对象相同)
foreach(KeyValuePair«string, int» thing in things)
{.......}
·使用不区分大小写的方法来比较字符串
Dictionary«string, int» things = new Dictionary«string, int»(StringCompare.CurrentCultureIgonreCase);
此时,如果执行下面的操作,会抛出ArgumentException异常
things.Add("GREEN", 29);
things.Add("green",94); //因为不区分大小写,以上两个实例拥有相同的键值,所以会异常。
34.泛型
定义泛型类
无绑定类型:
class MyGenericClass«T» {.......}
class MyGenericClass«T1,T2,T3» {.......}
class MyGenericClass«T1,T2,T3»
{
private T1 innerT1Object;
public MyGenericClass(T1 item) {innerT1Object = item;}
// innerT1Object = new T1(); //这行代码不能编译通过,因为不能假定T1是什么类型。
//不知道T1是什么也就不能使用它的构造函数(因为T1可能没有可公共访问的构造函数)。
.....
}
default关键字:
public MyGenericClass()
{ innerT1Object = default(T1); } //不能赋给innerT1Object的值为null,因它可能是值类型。
//所以用default,会赋给innerT1Object合适的默认值,比如,引用型赋值为null,整型赋值为0等等。
约束类型:
? 疑问-»P282中,为什么MyGenericClass«Cow» = new MyGenericClass«Cow»是可以编译的?
where关键字:
class MyGenericClass«T» where T: constraint1, constraint2 {...........}
// 其中constraint1 & constraint2为约束条件。 可以n个约束条件,用逗号分开。
class MyGenericClass«T1, T2» where T1: constraint1 where T2: constraint2
// 也可以用多个where约束
class MygenericClass«T1,T2» : MyBaseClass, IMyInterface where T1: constaint1 where T2: constaint2 {........}
// 约束要在继承说明符后面
class MyGenericClass«T1,T2» where T2:T1 {...} //这称为“裸类型约束”,一个泛型 做 另一个泛型的约束。 但不能循环,如:...where T2: T1 where T1:T2
一些可用的约束:struct, class, base-class, interface, new() 见书P283 表 12-5 。如果new()作约束,它必须是最后一个约束。
例:
public abstract class Animal {......}
public class Chicken : Animal {......}
public class Cow: Animal {....}
public class SuperCow : Cow {......}
public class Farm«T» : IEnumerable«T» where T :Animal
{
private List«T» animals = new List«T» ();
public List«T» Animals { set { return animals;} }
public IEnumerator«T» GetEnumerator() {return animals.GetEnumerator();} //实现了IEnumerable«T»接口,这样,Farm«T»可以直接迭代(如:foreach(Cow cow in
//Farm«Cow»)),不需要用Farm«T».Animals(如:foreach (Animal animal in Farm«T».Animals ))。
IEnumerator IEnumerable.GetEnumerator() {return animals.GetEnumerator();} //另一方面,因为IEnumerable«T»继承自IEnumerable,所以还需要实现IEnumerable
.......
}
从泛型类中继承:
class SuperFarm«T» : Farm«T» where T:SuperCow {.....} //Farm«T»中T被约束为Animal,而SuperCow为Animals的子集,所以可以约束。
//如果SuperCow换成 struct(因为T 不能转换为Farm«T»中的T) 或 class(是 T 的超集)就不能编译。
//结论: SuperFarm«T»继承 Farm«T»,所以,SuperFarm«T» 至少要受到 Farm«T»所受到的约束。
public class Cards: List«Card» ,ICloneable {...} // 继承一个泛型类,必须提供所有必须信息。
// 如果,List«Card»换成 List«T»那么编译会失败。
泛型运算符:
隐式转换运算符:
public static implicit operator List«Animal» (Farm«T» farm)
{
List«Animal» result = new List«Animal»();
foreach (T animal in farm)
{
result.Add(animal);
}
return result;
}
重载运算符:
public static Farm«T» operator + (Farm«T» farm1, List«T» farm2)
{
Farm«T» result = new Farm«T»();
foreach (T animal in farm1)
{
result.Animals.Add(animal);
}
foreach ( T animal in farm2)
{
if (!result.Animals.Contains(animal)) { result.Animals.Add();}
}
return result;
}
public static Farm«T» operator +(List«T» farm1 , Farm«T» farm2) {return farm2 + farm1;} // 注意 “重载+” 时的参数, 第一个是 Farm«T»+ List«T», 第二个是List«T»+ Farm«T»
--------------------------------
另一种方法:
public static Farm«T» operator +(Farm«T» farm1, Farm«T» farm2) {..............}
public static implicit operator Farm«Animal» (Farm«T» farm) {........}
//隐式转换,将对象转换为Farm«Animal»类型,然后,可以利用重载的 “+” 进行运算。
定义泛型方法:
非泛型类的 泛型方法:
public class Defaulter
{
public T GetDefault«T» () {.....}
}
泛型类的 泛型方法:
public class Defaulter«T»
{
public T GetDefault«T1»(){........} // T1 和 T 不能相同,否则编译失败。
}
泛型方法的重载:
public void ProcessT«T» (T op1) {.......}
public void ProcessT«T, U» (T op1, U op2) {.......}
使用哪个方法,取决于 参数 的个数。
35. ::运算符 & 全局名称空间限定
:: 用于别名访问
using MyAlias = MyRootSpace.Myspace; // 设定 别名 MyAlias
namespace MyRootNamespace
{
namespace MyAlias // 写一个与 别名 相同的 命名空间
{
public class MyClass {.........}
}
namespace Myspace // 这个是设定到 “别名” -MyAlias 的 命名空间
{
public class MyClass {.........}
}
}
************
//应用方式
MyAlias.MyClass 引用的是 MyAlias命名空间的 类
MyAlias::MyClass 引用的是 Myspace 命名空间的 类
---------------------------------------------------------------------
:: 还经常 和 global 关键字 一起使用
global是 “顶级根名称空间” 的 别名
global::System.Collections.Generic.List«int»
在大项目中才会出现,防止名称空间重名造成的引用混乱。
36. 定制异常
异常的基类:System.Exception 和 System.ApplicationException
System.SystemExceptiron用于.NET Framework预定义的异常的基类。
System.ApplicationException由开发人员用于派生自己的异常类。
这两个基类派生于Exception,但都没有扩展Exception类的功能。可以继承Exceptiron进行定制,但最好通过分类,继承上面两个基类。有助于分清是那种异常。
例:
public class CardOutOfRangeExceptiron:Exception //定制异常类
{
private Cards deckContents;
public Cards DeckContents{ get {return deckContents;}}
public CardOutOfRangeExceptrion(Cards sourceDeckContents) : base("There are only 52 cards in the deck.") {deckContents = sourceDeckContents;}
}
//******* 在方法中使用定制异常**********
public Card GetCard(int cardNum)
{
if (cardNum »= 0 && cardNum «= 51 )
return cards[cardNum];
else
throw new CardOutOfRangeException(cards.Clone() as Cards);
}
//********测试************
Deck deckTest = new Deck();
try{ Card myCard = deckTest.GetCard(60);} //GetCard(60)中60超出范围,引发异常
catch (CardOutOfRangeException e)
{
Console.WriteLine(e.Message); // 抛出在定制异常时,用基类初始化的提示(e.Message)
Console.WriteLine(e.DeckContents[0]); // 通过定制异常,获取对象(e.DeckContents[0])。
}
//*******输出结果:**********
There are only 52 cards in the deck.
The ace of clubs
36. 事件
自定义事件:
public delegate void MessageHandler(string messageText); // 第一步: 定义委托类型
......
public class Connection
{
public event MessageHandler MessageArrived; // 第二步: 声明事件。 用event关键字 和 要使用的委托类型 MessageHandler。
.......
private void CheckForMessage(object source, MessageEventArgs e)
{
....
if(MessageArrived != null) // 判断MessageArrived是否有引用事件处理程序。用+=订阅事件(把事件与处理程序关联起来) 。
{ MessageArrived("Hello Mum!"); } // 第三步: 引发事件。 (此例中,是在 另一个事件处理程序中进行引用)
}
}
*********订阅事件**********
public class Display
{
public void DisplayMessage(string message) { Console.WriteLine("Message arrived: {0}", message );} //定义事件处理程序
}
static void Main(string[] args)
{
........
Connection myCon = new Connection();
Display myDisplay = new Display();
mycon.MessageArrived += new MessageHandler(myDisplay.DisplayMessage); // 用+=订阅事件。 MessageArrived为前面声明的事件;
//MessageHandler为前面定义的委托类型;
//DisplayMessage为定义的事件处理程序,与 委托MessageHandler的签名相同。
........
}
---------------------------------------------------------------------------------
事件的参数:
前面 例子中的 委托 包含了事件处理程序中常见的两个参数: object source & MessageEventArgs e。
这两个参数可以在定义委托时自己定义,比如: public delegate void MessageHandler(Connection source, MessageArrivedEventArgs e); //Connection为前面定义的类
自定义事件参数:
public class MessageArrivedEventArgs : EventArgs // System.EventArgs
{
private string message;
public string Message {get { return message;}}
public MessageArrivedEventArgs() {.......}
..........
}
利用派生于 System.EventArgs类的参数,可以提供更多的必要信息。另外,还可以实现参数的多态性。如下:
Console.WriteLine( ( (MessageArrivedArgs)e ).Message );
---------------------------------------------------------------------------------
返回值 和 事件处理程序:
推使用 void 类型的事件处理程序,且避免使用out类型参数。
但如果事件处理程序有返回类型,那么当调用好几个事件处理程序时,返回最后一个订阅的事件处理程序返回的值。
--------------------------------------------------------------------------------
匿名方法:
匿名方法不是某个类上的 方法,而纯粹是为用作委托目的而创建的。
创建匿名方法的代码:
delegate(parameters)
{
// 匿名方法代码
}; //注意这里有个分号!!
例:
myConnection.MessageArrived += deleage(Connection source, MessageArrivedEventArgs e) { Console.WriteLine(source.Name);......};
=================================================================================================================================
C# 3.0部分
38.自动属性 和 部分方法
自动属性: public string Name{get;set;}
39.初始化器
初始化器会首先调用默认初始化函数,如没有,有时候编译器会自动提供一个。有时则必须提供一个。
对象初始化器:
例:
public class Curry // 定义一个类
{
public string MainIngredient {get; set;}
public string Style {get; set;}
public int Spiciness{get; set;}
......
}
*************************
Curry testCurry = new Curry
{
MainIngredient = "panir tikka", // 初始化时,各个参数用","分隔
Style = "jalfrezi",
Spiciess = 8
}; //注意有分号,可以把代码写在一行
********************************
如果,类型比较复杂,初始化器可以进行嵌套:
Curry testCurry = new Curry
{
MainIngredient = "panir tikka",
......
Orign = new Restaurant { name = "King's Balti", Location = "York Road",.....};
};
集合初始化器(常常会与LINQ技术一起使用):
例:
List«Curry» moreCurries = new List«Curry»
{
new Curry { MainIngredient = "Chicken", Style = "Pathia", Spiciness = 6 },
new Curry { MainIngredient = "Vegetable", Style = "Korma", Spicines = 3 },
new Curry { MainIngredient = "Prawn", Style = "Vindaloo", Spiciness = 9}
};
可以代替:
List«Curry» curries = new List«Curry»();
curries.Add(new Curry("Chicken","Pathia",6));
......
curries.Add(new Curry("Prawn","Vindaloo",9));
***************************
public class Animal {.............}
public class Cow:Animal {......}
public class Chicken:Animal {.......}
public class SuperCow:Animal {......}
static void Main(string[] Args)
{
Farm«Animal» farm = new Farm«Animal»
{
new Cow {Name = "Norris"},
new Chicken {Name = "Rita"},
new Chicken();
new SuperCow{ Name = "Chesney"}
};
................
}
要是上面的初始化器能够有效需要做以下两件事:
·i 给派生于Animal的类添加默认构造函数。Name属性可以利用基类的默认构造函数初始化。 public Animal(){name = “the animal is no name” ;}
·ii 为Farm«T» 添加Add方法:
public Farm«T» : IEnumerable«T» where T: Animal
{
public void Add(T animal) { animal.Add(animal);} // 添加Add方法
}
因为编译器在集合初始化器中要为提供的每一项调用Add()方法。在上面的初始化器中编译器猜不出是要用Animal.Add()填充属性,所以要在Farm«T»中添加Add方法。
也可以用下面的方法初始化:
Farm«Animal» farm = new Farm«Animal»
{
Animal = // 这样就不需要为Farm类添加Add()方法了
{
new Cow {Name = "Norris"},
new Chicken {Name = "Rita"},
new Chicken();
new SuperCow{ Name = "Chesney"}
}
};
40. 推断类型 var
var并不是声明了一个没有类型的变量(C#是强类型化的语言)。
在用var生命变量时,必须同时初始化该变量,以便让编译器确定变量类型。如果编译器不能确定,就不能编译。
比如:
var myVar = 5; // 此时,myVar被确定为int型。
var myArray = new[] {4,5,2};
在隐式指定数组类型时,初始化器中使用的数组元素必须是:
·相同的类型
·相同的引用类型 或 空
·所以元素的类型都可以隐式的转换为一个类型
总之,如果如能确定var变量的类型,编译就无法通过。
41. 匿名类型
常常有一些类只是提供属性。对底层数据模式的任何修改都要添加,删除或修改定义类的代码。
匿名类可以简化这个编程模式的一种方式。
例:
var curries = new[]
{
new { MainIngredient="Lamb", Style="Dhansak", Spiciness = 5},
new { MainIngredient="Lamb", Style="Dhansak", Spiciness = 5},
new { MainIngredient="chicken", Style="Dhansak", Spiciness = 5},
};
在IDE中查看var test = new {MainIngredient="Lamb", Style="Dhansak", Spiciness = 5};中test的类型是'a, '符号在IntelliSense中表示匿名类型。
匿名类型是只读的,不能修改。
在上面实例化的过程中,编译器会对比所创建的实例,会创建一个类型的三个实例,而不是三个类型。但不会在程序中查找相对应的类型进行匹配。
42. 扩展方法
扩展方法,可以扩展类型的功能,但无需修改类型本身。甚至可以扩展不能修改的类型,包括.net framework中定义的类型。
语法:
public static class ExtensionClass //类需要时静态类
{
public static ReturnType ExtensionMethodName (this TypeToExtend instance) //方法也要是静态的,this关键字必须有,TypeToExtend是要扩展的类。此处可以有多个参数。
{
.....
}
}
调用方式: TypeToExtend myVar; myVar.ExtensionMethodName(); 或 ExtensioClass.ExtensionMethodName(myVar);
例:
public static class ExtensionLibExtensions
{
public static string ToTitleCase(this string input string, bool forceLower)
{
inputString = inputString.Trim();
if(...)......
}
}
在程序中调用:
...
using ExtensionLib; //类ExtensionLibExtensions所在的命名空间
...
static void Main(string[] args)
{
...
string sourceString = Console.ReadLine();
sourceString.ToTitleCase(true); //true为方法声明中的第二个参数 bool foreceLower.
}
43. λ表达式
λ表达式用于简化C#编程的某些方面,尤其是与LINQ合并的方面。
把λ表达式用于匿名方法:
例:
myTimer.Elapsed += (source, e) =» Console.WriteLine("Event handler called after {0} milliseconds.", (source as Timer).Interval ); //应用λ表达式。
//(source, e)参数应用了匿名类型,会根据上下文推到类型。
// =»把参数列表 和 表达式体分开。
可以代替内联的匿名方法:
myTimer.Elapsed += delegate(object source, ElapsedEventArgs e) {Console.WriteLine("Event handler called after {0} milliseconds.", (source as Timer).Interval );};
这两段代码会被编译为相同 或 相似 的 MSIL代码。
λ表达式的参数:
上面的(source,e)采用了匿名类型。实际上也可以定义类型如: (int paramA , int paramB)=» paramA+ paramB
但不可以(int paramA, paramB)=»....., 只能要么全部指定类型,要么全部采用匿名类型。 不能混用。
如果,只有一个参数可以省略(),如: param1 =» param1*param1
还可以,定义 无参数 的λ表达式,使用空括号来表示:() =» Math.PI
λ表达式的语句体:
多行表达式时如下:
(param1, param2)=»
{
// 多行表达式
return returnvalue; //如果有返回类型,可以使用return语句。
}
例:
PerformOperations((int paramA, int paramB)=»param1+param2);
可以改写为:
performOperations(delegate(int paramA, int paramB) {return paramA+ paramB;});
还可改写为:
performOperations((int paramA, int paramB) =» { return paramA+paramB;});
单一表达式时,使用λ表达式比较简单。多个语句是,建议使用非匿名方法,这便于代码重用。
λ表达式用做委托 和 表达式树:
λ表达式有两种解释:
·i 解释为 委托 (如上面所描述的一样)
一般可以把拥有至多4个参数 的表达式 表示为如下 泛型类型:
·Action«»表示 不带 参数, 返回 void
·Action«»表示 至多4个参数, 返回 void
·Func«»表示 至多5个参数,返回非 void类型 。 返回类型放在参数列表最后。如:Func«int, int, int» 意思是,有两个int参数,返回类型是int。
·ii 解释为 表达式树(与LINQ关系密切)
表达式树 是 λ表达式 的抽象表示,但不能直接执行。可以使用表达式树已编程方式分析λ表达式,执行操作,以响应λ表达式。
例:
LINQ架构包含一个泛型类Expression«»,用于封装λ表达式。使用这个类的一种方式是提取C#编写的λ表达式,把它转换为等价的SQL脚本,以便在数据库中直接执行。
λ表达式 和 集合:
学习Func«»泛型委托后,理解System.Linq空间中为数组提供的扩展方法。例Aggregate()定义的其中一个版本:
public static TSource Aggregate «TSource»(this IEnumerable«TSource» source, Func«TSource, TSource, TSource» func );
调用:
int[] myIntArray = {2,6,3};
int result = myIntArray.Aggregate((param1, param2)=»param1+param2); //这个表达式会使 λ表达式调用两次(有三个数,先计算2+6,再计算8+3)。最后result=11。