C#与C++比较概况:
C++ | C# |
---|---|
#include <iostream> using namespace std; using namespace China::GuangDong::ShenZhen; namespace China { namespace HuBei { namespace WuHan { class Foo { public: static void Func() { cout << "Hello World!" << endl; } }; } } }
|
using System; using System.IO; using China.GuangDong.ShenZhen; namespace China { namespace HuBei.WuHan { class Foo { public static void Func() { Console.WriteLine("Hello World!"); } } } }
|
C#具有如下一些特性:
1. C#是强类型语言,所有变量都必须清楚标记为某个特定数据类型
2. C#中基本类型、枚举、结构体为值类型,数组、类(string、object及自定义类)、接口为引用类型
3. 值类型创建在栈上,引用类型创建在托管堆上(由GC来自动管理)
4. 局部变量、out参数变量要在显示初始化赋值后才能访问
5. 成员变量、static成员变量,在没有被显示初始化时,都会被编译器在构造函数或静态构造函数中默认赋初始值,其规则为:值类型为0或false,引用类型为null 详见:en
6. 大小写敏感:关键字、宏、变量名、函数名、命名空间名
7. 没有全局变量、没有全局静态变量、没有局部静态变量、没有全局函数,所有东西必须写入结构体或类中
8. 所有类型(含值类型)直接或间接都从object类继承
9. 类单根继承,接口多继承
10. 支持运算符重载(与c++相比,运算符重载函数为public static的)
11. 变量、属性和函数默认为private类型
12. 将程序集添加到工程后,无需显示包含其文件或导入包,带上完整的命名空间即可使用其定义的类型
13. 有构造函数和静态构造函数,没有析构函数,通过引用计数机制进行垃圾回收
14. 成员函数和成员属性默认不是virtual的
值类型与引用类型的补充说明:
值类型和引用类型都继承自System.Object类,不同的是,值类型从System.Object的子类System.ValueType继承。
System.ValueType本身是一个类类型,而不是值类型;System.ValueType没有添加任何成员,但覆盖了所继承的一些方法,使其更适合于值类型。
如:System.ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。
C#预定义基本类型
名称 | CTS类型 | 范围or精度 | 示例 |
sbyte | System.SByte | 8位:-128~127(-2^7~2^7-1) SByte.MinValue~SByte.MaxValue | -123、88、0x1C |
short | System.Int16 | 16位:-32768~32767(-2^15~2^15-1) Int16.MinValue~Int16.MaxValue | -123、88、0x1C |
int | System.Int32 | 32位:-2147483648~2147483647(-2^31~2^31-1) Int32.MinValue~Int32.MaxValue | -123、88、0x1C |
long | System.Int64 | 64位:-9223372036854775808~9223372036854775807(-2^63~2^63-1) Int64.MinValue~Int64.MaxValue | 123L、-123、88、0x1C |
byte | System.Byte | 8位:0~255(0~2^8-1) Byte.MinValue~Byte.MaxValue | 88、0x1C |
ushort | System.UInt16 | 16位:0~65536(0~2^16-1) UInt16.MinValue~UInt16.MaxValue | 88、0x1C |
uint | System.UInt32 | 32位:0~4294967295(0~2^32-1) UInt32.MinValue~UInt32.MaxValue | 88、0x1C、123U、225u |
ulong | System.UInt64 | 64位:0~18446744073709551615(0~2^64-1) UInt64.MinValue~UInt64.MaxValue | 88、0x1C、123UL、225ul |
float | System.Single | 32位单精度浮点数:6~9位有效数字 Single.MinValue~Single.MaxValue | -123、88、0x1C、100.0f、-235.68F |
double | System.Double | 64位双精度浮点数:15~17位有效数字 Double.MinValue~Double.MaxValue | -123、88、0x1C、320.5、-0.0325 |
decimal | System.Decimal | 128位高精度浮点数:28~29位有效数字 Decimal.MinValue~Decimal.MaxValue | -123、88、0x1C、-3.0m、25.6M |
bool | System.Boolean | true、false | |
char | System.Char | 16位Unicode字符 Char.MinValue~Char.MaxValue | ‘c‘、‘R‘、\u0041、\n、‘\x0061‘ |
一些用法:
int a = 2; int a = new int(); // 等价于int a = 0; int a = default (int); // 等价于int a = 0;
C#预定义引用类型
名称 | CTS类型 | 说明 |
object | System.Object |
根类型,CTS中其他类型都是从Object派生而来(包括值类型) 基本方法(派生类可以重写这些方法来实现自己的逻辑): bool Equals(Object) // 判断两个对象是否相等 int GetHashCode() // 获取当前对象的HashCode(每个对象的HashCode是唯一的) Type GetType() // 获取当前对象的类型 string ToString() // 将当前对象转换成string类型 |
数组 | System.Array | |
string | System.String |
Unicode字符串,"Hello World!\n" "" "你好" "c:\\1.txt" @"d:\Music\1.mp3" "\u6700\u540e\u4fee\u6539\u7684\u7248\u672c:"//最后修改的版本: Unicode在线转换工具:http://tool.chinaz.com/tools/unicode.aspx 注:在@字符后的所有字符都看成原来的含义,甚至包含换行符(将字符串写成多行时) 字符串虽然是引用类型,但用起来像值类型:原因是string重写了赋值运算符 string s1 = "Hello"; string s2 = s1; // s1、s2都指向"Hello" s1 = "World"; // s1重新赋值指向"World",s2依然指向"Hello"
|
一些用法:
object o = null; object o = default(object);
基本类型与string间转换
1. 基本类型 --> string,可以调用对应CTS类型中的ToString方法
2. string --> 基本类型,可以调用对应CTS类型中的Parse和TryParse方法。如果string不满足转换要求,Parse方法将会引发一个异常,TryParse方法不会引发异常,它会返回fasle。
各类型之间转换
2. 使用Convert类进行基本类型、string、DateTime结构体之间的转换
int i = 10; float f = i;//f=10 byte b = (byte)i;//b=10 string s1 = "123.5"; f = Convert.ToSingle(s1); //f=123.5 string s2 = "123abc"; i = Convert.ToInt32(s2); //抛出System.FormatException异常
3. 子类对象可以隐式转换为父类对象,父类对象必须显示转换为子类对象(若类型不匹配,则抛出‘System.InvalidCastException异常)
4. 值类型可以隐式转换为object类型(装箱),object类型必须显示转换为值类型(类型必须强匹配,int装箱的object只能拆箱成int,不能是long、float等;若类型不匹配,则抛出‘System.InvalidCastException异常)
checked / unchecked
1. 编译器默认是关闭溢出检查的,建议在测试时开启该checked编译选项
2. checked/unchecked开启/关闭溢出检查 注:若开启了checked溢出检查,运行时发生溢出会抛出OverflowException异常
byte b1 = 255; int i1 = int.MinValue; try { checked { try { unchecked { b1++; // 不对b1进行溢出检查 } } catch (OverflowException) { Console.WriteLine("b1 OverflowException."); // 不会打印该log } CheckFunc(); // 不会对调用的函数进行溢出检查 i1--; // 检查发现i1溢出,抛出异常 } } catch (OverflowException) { Console.WriteLine("i1 OverflowException."); // 会打印该log }
注:checked或者unchecked只影响其包围的语句,不会影响到包围的语句内调用函数
is、as运算符
int i1 = 10; string s1 = "Hello"; object o1 = "Hi"; object o2 = 100; if (i1 is object) // true { Console.WriteLine("i1 is object"); } if (s1 is object) // true { Console.WriteLine("s1 is object"); } if (o2 is string) // false { Console.WriteLine("o2 is string"); } object i2 = i1 as object; // i1=10被装箱 string s2 = o1 as string; // s2="Hi" string s3 = o2 as string; // s3=null
sizeof
1.只能用于值类型(基本类型、枚举和只包含值类型的结构体)
2.只包含值类型的结构体需要在unsafe块中使用
struct Color { public float R; public float G; public float B; public float A; } int i1 = 10; object i2 = 20; string s1 = "Hi"; Console.WriteLine(sizeof(int)); //4 //Console.WriteLine(sizeof(i1)); //编译不过 Console.WriteLine(sizeof(double)); //8 Console.WriteLine(sizeof(TimeOfDay));//TimeOfDay枚举类型的size为4 Console.WriteLine(System.Runtime.InteropServices.Marshal.SizeOf(i1)); //4 Console.WriteLine(System.Runtime.InteropServices.Marshal.SizeOf(i2)); //4 //Console.WriteLine(System.Runtime.InteropServices.Marshal.SizeOf(int));//编译不过 unsafe // 编译设置中需要勾选Allow unsafe code { Console.WriteLine(sizeof(Color)); // 16 } Console.WriteLine(System.Runtime.InteropServices.Marshal.SizeOf(s1)); //抛异常System.ArgumentException
typeof
namespace ConsoleApplication1 { enum TimeOfDay { Moring = -10, Afternoon = -11, Evenving } struct Color { public float R; public float G; public float B; public float A; } } Type t1 = typeof(TimeOfDay); Type t2 = typeof(Color); Console.WriteLine(t1);//ConsoleApplication1.TimeOfDay Console.WriteLine(t2);//ConsoleApplication1.Color Console.WriteLine(typeof(int));//System.Int32 Console.WriteLine(typeof(string));//System.String
可空类型? 派生于结构体Nullable<T>
1. 可空类型与其他可空类型之间的转换,遵循非可空类型的转换规则:如:int?可隐式转换为long?、float?、double?和decimal?
2. 非可空类型与其他可空类型之间的转换,遵循非可空类型的转换规则:如:int可隐式转换为long?、float?、double?和decimal?
3. 可空类型必须显示地转换为非可空类型(若可空类型为null,转换时抛System.InvalidOperationException异常)
4. 可空类型与一元或二元运算符一起使用时,如果其中一个操作数或两个操作数都是null,其结果就是null
int? a = null; int? b = a + 1;//b=null int? c = a * 2;//c=null int? d = 3;//d=3 //比较的2个数中只要有一个为空类型则为false if (a >= d) { Console.WriteLine("a>=d"); } else if (a < d) { Console.WriteLine("a<d"); } else { Console.WriteLine("Oh, My God!");//该行被打印 }
空合并运算符??
1. 空合并运算符:若第一个操作数不是null,表达式为第一个操作数的值;若第一个操作数为null,则表达式为第二个操作数的值
int? a = null; int? b = 3;//b=3 int c = a ?? 4;//a为空类型,则把??后面的数4赋值给c int d = b ?? 5;//b不为空类型,则把b赋值给d
空条件运算符?.和?[] 【c#6.0】
1. 实例在访问成员或数组访问元素前自动判空,不用显示添加手动判空代码,让程序更优雅
int? len = customers?.Length; //null if customers is null 等价于int? len = (customers==null?null:customers.Length) Customer first = customers?[0]; // // null if customers or customers[0] is null 等价于Customer first = (customers==null?null:(customers.Length==0?null:customers[0])) int? count = customers?[0]?.Orders?.Count(); // 等价于int? count = (customers==null?null:(customers.Length==0?null:(customers[0]==null?null:(customers[0].Orders==null?null:customers[0].Orders.Count))))
条件语句
1. swith case可以把字符串当作测试变量
switch (country) { case "China": break; case "America": break; default: break; }
循环语句
1. foreach循环
foreach (int n in arrayOfInts) { Console.WriteLine(n); }
命名空间别名
除了C#与C++比较概况中提到的命名空间的用法外,C#还允许使用别名来简化类型对命名空间的使用(注:通过::符号来使用)
using System; using InterSrv = System.Runtime.InteropServices; byte a = 3; int nLen = InterSrv::Marshal.SizeOf(a.GetType()); Console.WriteLine("Type is {0}, Length is {1}.", a.GetType().ToString(), nLen); // Type is System.Byte, Length is 1.
函数参数传递 【ref out】
int a1 = 1, b1 = 2; Func(a1, b1);//函数返回后,a1=1 b1=2 int a2 = 1, b2 = 2; FuncRef(ref a2, ref b2);//函数返回后,a2=2 b2=1 int a3 = 1;//由于out参数不会使用该初值,因此a3可以不用赋初值 FuncOut(out a3);//函数返回后,a3=5 CA oa1 = null; Func2(oa1);//函数返回后,oa1=null CA oa2 = null; FuncRef2(ref oa2);//函数返回后,oa2不为null,oa2.Id=100 CA oa3 = new CA();//由于out参数不会使用该初值,因此oa3可以不用赋初值 FuncOut2(out oa3);//函数返回后,oa3=null void Func(int a, int b) { int c = a; a = b; b = c; } void FuncRef(ref int a, ref int b) { int c = a; a = b; b = c; } void FuncOut(out int a) { //int b = a; //在函数中未给out参数变量a赋值之前,不能访问a a = 5;//在函数返回之前必须要对a进行赋值 } void Func2(CA o) { o = new CA(); o.Id = 100; } void FuncRef2(ref CA o) { o = new CA(); o.Id = 100; } void FuncOut2(out CA o) { //bool b = (o != null);//在函数中未给out参数变量o赋值之前,不能访问o o = null;//在函数返回之前必须要对o进行赋值 }
命名参数 【c#4.0】
string s = GetFullName(LastName: "Li", FirstName: "Lei"); string GetFullName(string FirstName, string LastName) { return FirstName + " " + LastName; }
缺省参数 【c#4.0】
string GetFullName(string FirstName, string LastName="Chen") { return FirstName + " " + LastName; }
数组型参数
params会把传入各个参数存放在一个object数组中
1.params参数必须放在最后
2.函数只允许有一个params参数
public static void Test(int n, params object[] list) { Console.WriteLine(n); for (int i = 0; i < list.Length; i++) { Console.WriteLine(list[i]); } } Test(100, false, 3, "Test");//打印出:100 False 3 Test
类型推断 【c#3.0】
1.推断出了类型后,就不能改变类型了
2.类型确定后,就遵循强类型化规则
var name = "Jim Green"; var age = 20; var height = 1.825f; var sex = true; //var weight; 编译不过,必须在声明时初始化 //weight = 60.5; Console.WriteLine("name‘s type is {0}", name.GetType());//name‘s type is System.String Console.WriteLine("age‘s type is {0}", age.GetType());//age‘s type is System.Int32 Console.WriteLine("height‘s type is {0}", height.GetType());//height‘s type is System.Single Console.WriteLine("sex‘s type is {0}", sex.GetType());//sex‘s type is System.Boolean Object o = new Object(); var o1 = new Object(); var o2 = o; //var friend = null;编译不过,不能为空
匿名类型
var与关键字new一起使用可以创建匿名类型;匿名类型只是一个从Object派生没有名称的类,该类的定义从初始化器中推断
匿名类型名有编译器按照一定规则产生,我们不应该使用任何该类型的反射
//生成一个包含string FirstName, string MiddleName, string LastName属性的类型,并用该类型初始化出个名为captain的实例 var captain = new { FirstName = "James", MiddleName = "T", LastName = "Kirk" }; //生成一个包含string Location, int Area, double Population属性的类型,并用该类型初始化出个名为china的实例 var china = new { Location = "Asia", Area = 960, Population = 1.3 }; // doctor与captain的变量名、类型、顺序完全一致,为同一匿名类型;因此,不会创建新的类型,而是使用captain类型来初始化captain var doctor = new { FirstName = "Leonard", MiddleName = "T", LastName = "McCoy" }; Console.WriteLine("captain‘s type is {0}", captain.GetType());//captain‘s type is <>f__AnonymousType0`3[System.String,System.String,System.String] Console.WriteLine("china‘s type is {0}", china.GetType());//china‘s type is <>f__AnonymousType1`3[System.String,System.Int32,System.Double] Console.WriteLine("doctor‘s type is {0}", doctor.GetType());//doctor‘s type is <>f__AnonymousType0`3[System.String,System.String,System.String]
格式化输出到控制台
格式符形如:{D[,D[:C[D]]]} -- D、D、D表示数字,C表示字母
D:后续参数序号,从0开始
D:占位数目(占位数少于变量自身数目时,不产生多余占位;货币符号算1个占位):为正数,表示右对齐;为负数,表示左对齐
D:整型数时,为整个数的位数,不够时左边补0;浮点数时,为小数点后的位数
C:① D -- 十进制格式
② E、e -- 科学计数法,默认小数点后的位数为6
③ F -- 浮点数格式
④ G -- 普通格式
⑤ N -- 数字格式,逗号表示千位符
⑥ P -- 百分数格式
⑦ X -- 十六进制格式
⑧ C -- 本地货币格式 中国区为:¥
int i = 262, j = 350; Console.WriteLine("{0}#{1}", i, j);//262#350 Console.WriteLine("{0,4}#{1,5}", i, j);// 262# 350 Console.WriteLine("{0,-4}#{1}", i, j);//262 #350 Console.WriteLine("{0,6:D5}#{1}", i, j);// 00262#350 int m = 26252, n = 350390; Console.WriteLine("{0,0:D}#{1,0:F}", m, n);//26252#350390.00 Console.WriteLine("{0,0:G}#{1,0:N}", m, n);//26252#350,390.00 Console.WriteLine("{0,0:E}#{1,0:e}", m, n);//2.625200E+004#3.503900e+005 Console.WriteLine("{0,0:X}#{1,0:P}", m, n);//668C#35,039,000.00% float f = 35.0f, g = -20.388f; Console.WriteLine("{0,7:F3}#{1,0:E2}", f, g);// 35.000#-2.04E+001 Console.WriteLine("{0,4:C2}#{1,0:e3}", f, g);//¥35.00#-2.039e+001 Console.WriteLine("{0,6:C1}#{1,0:P3}", f, g);// ¥35.0#-2,038.800%
字符串(16位unicode)
String处理不变的字符串,,不可被继承(sealed),任何对String的改变都会引发新的String对象的生成
string s0 = "你好"; int ns0Len = s0.Length; // ns0Len = 2 char[] sz = new char[] { ‘h‘, ‘e‘, ‘l‘, ‘l‘, ‘o‘ }; string s1 = "hello"; // 静态创建字符串对象 string s2 = new string(sz); // 动态创建字符串对象 int ns1Len = s1.Length; // ns1Len = 5 int ns2Len = s2.Length; // ns2Len = 5 string s3 = "hello"; string s4 = new string(sz); string s5 = "Hello"; if (s1 == s2) Console.WriteLine("s1==s2");// true if (s1.Equals(s2)) Console.WriteLine("s1 Equals s2");// true if (Equals(s1, s2)) Console.WriteLine("s1 static Equals s2");// true if (ReferenceEquals(s1, s2)) Console.WriteLine("s1 ReferenceEquals s2");// false if (s1.CompareTo(s2) == 0) Console.WriteLine("s1 Compare s2");// true if (string.Compare(s1, s2) == 0) Console.WriteLine("s1 static Compare s2");// true if (s1 == s3) Console.WriteLine("s1==s3");// true if (s1.Equals(s3)) Console.WriteLine("s1 Equals s3");// true if (Equals(s1, s3)) Console.WriteLine("s1 static Equals s3");// true if (ReferenceEquals(s1, s3)) Console.WriteLine("s1 ReferenceEquals s3");// true if (s1.CompareTo(s3) == 0) Console.WriteLine("s1 Compare s3");// true if (string.Compare(s1, s3) == 0) Console.WriteLine("s1 static Compare s3");// true if (s2 == s4) Console.WriteLine("s2==s4");// true if (s2.Equals(s4)) Console.WriteLine("s2 Equals s4");// true if (Equals(s2, s4)) Console.WriteLine("s2 static Equals s4");// true if (ReferenceEquals(s2, s4)) Console.WriteLine("s2 ReferenceEquals s4");// false if (s2.CompareTo(s4) == 0) Console.WriteLine("s2 Compare s4");// true if (string.Compare(s2, s4) == 0) Console.WriteLine("s2 static Compare s4");// true if (s1 == s5) Console.WriteLine("s1==s5");// false if (s1.Equals(s5)) Console.WriteLine("s1 Equals s5");// false if (Equals(s1, s5)) Console.WriteLine("s1 static Equals s5");// false if (ReferenceEquals(s1, s5)) Console.WriteLine("s1 ReferenceEquals s5");// false if (s1.CompareTo(s5) == 0) Console.WriteLine("s1 Compare s5");// false if (string.Compare(s1, s5) == 0) Console.WriteLine("s1 static Compare s5");// false if (s1.Equals(s5, StringComparison.OrdinalIgnoreCase)) Console.WriteLine("s1 OrdinalIgnoreCase Equals s5");// true if (string.Equals(s1, s5, StringComparison.OrdinalIgnoreCase)) Console.WriteLine("s1 OrdinalIgnoreCase static Equals s5");// true if (string.Compare(s1, s5, true) == 0) Console.WriteLine("s1 ignoreCase Compare s5");// true if (string.Compare(s1, s5, StringComparison.OrdinalIgnoreCase) == 0) Console.WriteLine("s1 OrdinalIgnoreCase Compare s5");// true
StringBuilder 处理可变字符串(拥有更高的性能),不可被继承(sealed)
string s1 = "Hello"; StringBuilder sb1 = new StringBuilder(s1); int nLengh1 = sb1.Length;//5 int nCapacity1 = sb1.Capacity;//16 int nMaxCapacity1 = sb1.MaxCapacity;//2147483647 StringBuilder sb2 = new StringBuilder(4, 8); int nLengh2 = sb2.Length;//0 int nCapacity2 = sb2.Capacity;//4 int nMaxCapacity2 = sb2.MaxCapacity;//8 sb2.Append("ab");//sb2="ab" sb2.Length=2 sb2.Append("cdef");//sb2="abcdef" sb2.Length=6 //sb2.Append("ghijk");//抛出System.ArgumentOutOfRangeException 容量小于当前大小 StringBuilder sb3 = new StringBuilder("abcdef");//false if (sb2 == sb3) Console.WriteLine("sb2==sb3");//false if (sb2.Equals(sb3)) Console.WriteLine("sb2 Equals sb3");//false if (Equals(sb2, sb3)) Console.WriteLine("sb2 static Equals sb3");//false if (ReferenceEquals(sb2, sb3)) Console.WriteLine("sb2 ReferenceEquals sb3");//false StringBuilder sb4 = sb3; if (sb3 == sb4) Console.WriteLine("sb3==sb4");//true if (sb3.Equals(sb4)) Console.WriteLine("sb3 Equals sb4");//true if (Equals(sb3, sb4)) Console.WriteLine("sb3 static Equals sb4");//true if (ReferenceEquals(sb3, sb4)) Console.WriteLine("sb3 ReferenceEquals sb4");//true StringBuilder sb5= new StringBuilder("World"); string s2 = sb5.ToString();//s2="World"
注释
除了与C++相同的单行注释// Line和多行注释/* Multi Lines */之外,还有用于帮助文档的注释符/// Help Documents
为了更好地生成帮助文档,C#定义了一套XML标签来描述注释中各个字符串的含义 如:summary、param、returns等
/// <summary> /// Add two integers /// </summary> /// <param name="a"></param> /// <param name="b"></param> /// <returns></returns> int Add(int a, int b) { return a + b; }
在项目“属性”--“Build”--Output下勾选“XML documentation file”:bin\Debug\ConsoleApplication1.XML
在编译时会检查XML标签的格式、及内容是否与代码一致,若有错误编译器会给出warning
ConsoleApplication1.XML的内容:
<?xml version="1.0"?> <doc> <assembly> <name>ConsoleApplication1</name> </assembly> <members> <member name="M:ConsoleApplication1.Program.Add(System.Int32,System.Int32)"> <summary> Add two integers </summary> <param name="a"></param> <param name="b"></param> <returns></returns> </member> </members> </doc>
预编译指令
C#的预编译指令是由编译器处理的
1. #define #undef 必须放在cs文件的起始处,符号存在,就被认为是true,否则为false
#define Run_Test //如果Run_Test已定义,则忽略该句 Run_Test=true #undef Run_Test //如果Run_Test未定义,则忽略该句 Run_Test=false #define Run_Test2 //如果Run_Test2已定义,则忽略该句 Run_Test2=true
2. #if...#elif...#else
#if Run_Test Console.WriteLine("Run Test."); #elif Run_Test2 Console.WriteLine("Run Test2."); //Run Test2. #else Console.WriteLine("No Run Test."); #endif #if Run_Test==false Console.WriteLine("No Run Test."); //No Run Test. #endif #if Run_Test || Run_Test2 Console.WriteLine("No Run Test or Run Test2."); //No Run Test or Run Test2. #endif #if Run_Test && Run_Test2 Console.WriteLine("Run Test and Run Test2."); #endif #if !Run_Test && Run_Test2 Console.WriteLine("No Run Test and Run Test2."); //No Run Test and Run Test2. #endif
3. #warning #error
#if DEBUG && !Run_Test #warning "You should define Run_Test in debug version." #endif #if DEBUG && Run_Test2 #error "Can‘t define Run_Test2 in debug version." #endif
4. #region #endregion 把一段代码标记为一个给定名称的块,该指令能被某些编辑器(如:vs、Notepad++)识别,使得代码在屏幕上更好的布局
#region size of Rectangle int m_nHeight; int m_nWidth; #endregion
5. #line 改变编译器警告和错误中显示的文件名和行号信息
6. #pragma 屏蔽或开启指定编译警告
定制特性(custom attribute)
定制特性是一种可扩展的元数据,通过运行时查询,从而动态改变代码的执行方式
.Net Framework类库定义了几百个定制特性,最常见如:DllImport(对应DllImportAttribute)
[DllImport("user32.dll", EntryPoint = "MessageBox", SetLastError = true, CharSet = CharSet.Unicode)] public static extern int WinMessageBox(IntPtr hWnd, String text, String caption, uint type);
使用Conditional定制属性来控制成员函数或Attribute类是否编译进二进制可执行文件
#define TRACE_ON public class TraceTest { [ConditionalAttribute("TRACE_ON")] public static void Msg(string msg) { Console.WriteLine(msg); } [Conditional("TRACE_ON")] public void TestRet(string msg) { Console.WriteLine(msg); } #if TRACE_ON public static void Msg2(string msg) { Console.WriteLine(msg); } #endif } [Conditional("TRACE_ON")] public class TraceTestAttribute : System.Attribute { public string text; public TraceTestAttribute(string text) { this.text = text; } }
定义TRACE_ON宏时,TraceTest类中会有Msg静态方法和TestRet成员方法,TraceTestAttribute类型可用
与传统的#if TRACE_ON #endif相比,这种方式不需要手动处理所有调用的地方(即不需要手动使用#if TRACE_ON #endif进行包裹)
示例
public class DocumentationAttribute : System.Attribute { public string text; public DocumentationAttribute(string text) { this.text = text; } } class SampleClass { // This attribute will only be included if DEBUG is defined. [Documentation("This method displays an integer.")] public static void DoWork(int i) { System.Console.WriteLine(i.ToString()); } } var lstProperties = typeof(SampleClass).GetMethods(); foreach (var oProperty in lstProperties) { foreach (Attribute attr in oProperty.GetCustomAttributes(true)) { DocumentationAttribute helpattr = attr as DocumentationAttribute; if (helpattr != null) { Console.WriteLine("Description of AnyClass: {0}", helpattr.text); // Description of AnyClass: This method displays an integer. } } }
装箱与拆箱
int a1 = 10; string s1 = a1.ToString(); // a1装箱成object对象 object o1 = a1; // a1装箱成object对象,然后赋值给o1 int a2 = (int)o1; // o1拆箱成int值类型 float f1 = (float)o1; // 引发System.InvalidCastException异常
相等性比较
静态ReferenceEquals方法:引用比较 若是值类型则会先被装箱,因此2个值类型比较会始终返回false
对象虚拟Equals方法:基本类型比较值(若被装箱,则会先拆箱再比较值),结构体会逐个比较各成员变量,都相等则返回true;引用类型默认比较引用,可重写。
如:string重写了该运算符,让其比较字符串的内容;而Array没有重写该运算符,会执行引用比较
静态Equals方法:内部会检查参数是否为null,2个都为null则返回true,只有1个为null则返回false,然后调用对象的虚拟Equals方法进行比较
==比较运算符:基本类型比较值,结构体需要重载==和!=运算符来定义比较行为;引用类型默认比较引用,可重写。
如:string重写了该运算符,让其比较字符串的内容;而Array没有重写该运算符,会执行引用比较
struct Color { public float R; public float G; public float B; public float A; public static bool operator ==(Color a, Color b) { return (Math.Abs(a.R - b.R) < float.Epsilon) && (Math.Abs(a.G - b.G) < float.Epsilon) && (Math.Abs(a.B - b.B) < float.Epsilon) && (Math.Abs(a.A - b.A) < float.Epsilon); } public static bool operator !=(Color a, Color b) { return !(a==b); } } int i1 = 10; int i2 = 100 / i1; if (ReferenceEquals(i1, i2)) Console.WriteLine("i1 ReferenceEquals i2");// false if (i1.Equals(i2)) Console.WriteLine("i1 Equals i2");// true if (Equals(i1, i2)) Console.WriteLine("i1 static Equals i2");// true if (i1 == i2) Console.WriteLine("i1 == i2");// true object o1 = i1; object o2 = i2; object o3 = o1; if (ReferenceEquals(o1, o2)) Console.WriteLine("o1 ReferenceEquals o2");// false if (o1.Equals(o2)) Console.WriteLine("o1 Equals o2");// true if (Equals(o1, o2)) Console.WriteLine("o1 static Equals o2");// true if (o1 == o2) Console.WriteLine("o1 == o2");// false if (ReferenceEquals(o1, o3)) Console.WriteLine("o1 ReferenceEquals o3");// true if (o1.Equals(o3)) Console.WriteLine("o1 Equals o3");// true if (Equals(o1, o3)) Console.WriteLine("o1 static Equals o3");// true if (o1 == o3) Console.WriteLine("o1 == o3");// true object o4 = new int[] { 1, 2, 3 }; object o5 = new int[] { 1, 2, 3 }; object o6 = o4; if (ReferenceEquals(o4, o5)) Console.WriteLine("o4 ReferenceEquals o5");// false if (o4.Equals(o5)) Console.WriteLine("o4 Equals o5");// false if (Equals(o4, o5)) Console.WriteLine("o4 static Equals o5");// false if (o4 == o5) Console.WriteLine("o4 == o5");// false if (ReferenceEquals(o4, o6)) Console.WriteLine("o4 ReferenceEquals o6");// true if (o4.Equals(o6)) Console.WriteLine("o4 Equals o6");// true if (Equals(o4, o6)) Console.WriteLine("o4 static Equals o6");// true if (o4 == o6) Console.WriteLine("o4 == o6");// true char[] sz = new char[] { ‘h‘, ‘e‘, ‘l‘, ‘l‘, ‘o‘ }; string s1 = new string(sz); string s2 = "hello"; string s3 = "hello"; if (ReferenceEquals(s1, s2)) Console.WriteLine("s1 ReferenceEquals s2");// false if (s1.Equals(s2)) Console.WriteLine("s1 Equals s2");// true if (Equals(s1, s2)) Console.WriteLine("s1 static Equals s2");// true if (s1 == s2) Console.WriteLine("s1 == s2"); // true if (ReferenceEquals(s2, s3)) Console.WriteLine("s2 ReferenceEquals s3");// true if (s2.Equals(s3)) Console.WriteLine("s2 Equals s3");// true if (Equals(s2, s3)) Console.WriteLine("s2 static Equals s3");// true if (s2 == s3) Console.WriteLine("s2 == s3"); // true Color sc1, sc2; sc1.R = 1.0f; sc1.G = 0.0f; sc1.B = 0.0f; sc1.A = 0.0f; sc2.R = 1.0f; sc2.G = 0.0f; sc2.B = 0.0f; sc2.A = 0.0f; if (ReferenceEquals(sc1, sc2)) Console.WriteLine("sc1 ReferenceEquals sc2");// false if (sc1.Equals(sc2)) Console.WriteLine("sc1 Equals sc2");// true if (Equals(sc1, sc2)) Console.WriteLine("sc1 static Equals sc2");// true if (sc1 == sc2) Console.WriteLine("sc1 == sc2"); // true int[] intArray1 = new int[] { 1, 2, 3 }; int[] intArray2 = new int[] { 1, 2, 3 }; int[] intArray3 = intArray1; if (ReferenceEquals(intArray1, intArray2)) Console.WriteLine("intArray1 ReferenceEquals intArray2"); // false if (intArray1.Equals(intArray2)) Console.WriteLine("intArray1 Equals intArray2"); // false if (Equals(intArray1, intArray2)) Console.WriteLine("intArray1 static Equals intArray2"); // false if (intArray1 == intArray2) Console.WriteLine("intArray1 == intArray2"); // false if (ReferenceEquals(intArray1, intArray3)) Console.WriteLine("intArray1 ReferenceEquals intArray3"); // true if (intArray1.Equals(intArray3)) Console.WriteLine("intArray1 Equals intArray3"); // true if (Equals(intArray1, intArray3)) Console.WriteLine("intArray1 static Equals intArray3"); // true if (intArray1 == intArray3) Console.WriteLine("intArray1 == intArray3"); // true
运算符重载和自定义类型强制转换
1. 运算符重载函数和自定义类型强制转换函数必须是public static的
2. 比较运算符重载(==和!=、>和<、>=和<=)必须成对编写,否则编译出错
3. 自定义类型强制转换函数分为显式的(explicit)和隐式的(implicit)
4. 不同类之间也可以自定义类型强制转换函数,前提是之间没有继承体系的父子关系
5. 多重类型强制转换隐式规则:如下面代码中,虽然MyColor没有定义double类型的转换,但可以通过ulong类型再隐式地转换成double类型。但若MyColor定义了double类型的转换,会调用double类型进行类型转换
class MyColor { public byte R, G, B, A; public static bool operator ==(MyColor a, MyColor b) { return (a.R == b.R) && (a.G == b.G) && (a.B == b.B) && (a.A == b.A); } public static bool operator !=(MyColor a, MyColor b) { return !(a == b); // 调用==运算符 } // 显式转换为string类型 public static explicit operator string(MyColor a) { return string.Format("{0}.{1}.{2}.{3}", a.R, a.G, a.B, a.A); } // 隐式转换为ulong类型 public static implicit operator ulong(MyColor a) { return a.R * 0xFFFFFFUL + a.G * 0xFFFFul + a.B * 0xFFul + a.A; } } MyColor mc = new MyColor(); mc.R = 255; mc.G = 0; mc.B = 0; mc.A = 0; MyColor mc2 = new MyColor(); mc2.R = 0; mc2.G = 255; mc2.B = 0; mc2.A = 0; if (mc != mc2) Console.WriteLine("mc != mc2"); string smc = (string)mc; // 255.0.0.0 ulong ulmc = mc; // 4278189825 double dmc = mc; // 4278189825.0 多重类型强制转换:虽然MyColor没有定义double类型的转换,但可以通过ulong类型再隐式地转换成double类型
扩展类的方法
当某个类xxx没有源码,且被sealed关键字修饰不允许被继承,我们可以通过静态类来扩展类xxx的成员函数 注:静态函数的第一个参数为this xxx类型
下面的示例中为string类型扩展了一个void Test(int n)方法
public static class stringEx { public static void Test(this string s, int n) { Console.WriteLine("s:{0} n:{1}", s, n); } } string s1 = "Hello"; s1.Test(100);// 打印出 s:Hello n:100
垃圾回收(GC)
1. 垃圾回收器管理的是托管堆内存,引用类型的对象不再被引用时,GC会自动在某个时刻将其回收其内存
2. GC可由CLR内部检查算法(如:某个固定时间间隔、程序内存占用过高等)来自动触发,也可以由开发者调用GC.Collect()函数来手动触发
3. GC被触发后,并不会立刻进行垃圾回收(该过程是异步的)
4. 垃圾回收器的逻辑不能保障在一次垃圾回收过程中,将所有未引用的对象从堆中删除
弱引用
当实例化一个类时,只要有代码引用它,就形成了强引用,使得GC无法回收该对象。
如果该对象很大但又不是经常访问,使得程序会保持很高的内存占用。可以使用弱引用来解决该问题。
弱引用允许GC回收其指向的对象,在使用对象前需通过IsAlive属性判断对象是否已被GC
class CA { public string Name; } class Program { static void Main(string[] args) { WeakReference refCA = new WeakReference(new CA()); if (refCA.IsAlive) { (refCA.Target as CA).Name = "Test1"; } else { Console.WriteLine("refCA is not available."); } GC.Collect();//调用垃圾回收器 发起GC 注:并不会立即回收new出来的CA对象 while (refCA.IsAlive) { System.Threading.Thread.Sleep(1000);//循环等待,确保GC已回收CA对象 } Console.WriteLine("CA object has been recycled by GC."); } }
枚举
派生于System.Enum
enum Days { Monday=1, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday}; int n1 = (int)Days.Tuesday;//n1=2 Days ed = Days.Sunday; int n2 = (int)ed;//n2=7 string s1 = ed.ToString(); // Sunday string s2 = Enum.GetName(typeof(Days), ed); // Sunday
权限控制
外部类(结构体)
internal(缺省):只有在同一程序集内才可以被访问
public:任何地方都可以被访问
内部类(结构体)& 类的成员
private(缺省):当前类(结构体)中可以被访问
protected:当前类(结构体)及其子类中可以被访问
public:任何地方都可以被访问
internal:只有在同一程序集内才可以被访问
protected internal:protected与internal求并集的范围内可以被访问
访问范围:public > protected internal > internal > protected > private
结构体 -- 值类型
(1)不能从其他结构体或类继承,但可以从接口继承
(2)不允许有protected或protected internal成员变量和成员函数(由于不能从其他结构体或类继承,protected或protected internal无意义)
(3)不允许有析构函数(终结器finalizer)
(4)不允许显示给出无参构造函数
(5)不能在非static字段后面对其赋初值,若无自定义带参构造函数时,这些非static字段会被编译器在无参构造函数中默认赋缺省初值
(6)static变量可以直接在后面对其赋初值,也可以在静态构造函数中赋初值;
(7)与static变量一样,const变量属于类,但其只能在后面对其赋值来初始化
(8)若有自定义带参构造函数,必须在其中对所有非static字段进行初始化
(9)无论是否有自定义带参构造函数,无参构造函数都默认存在,由结构体变量声明方式来决定调用哪个构造函数
(10)通过new来调用结构体的构造函数进行初始化,如:SA s = new SA() 或 SA s = new SA("Hello");
(11)允许有静态构造函数,但不允许带访问修饰符
(12)静态构造函数只会被执行一次,也就是在创建第一个实例或引用任何静态成员之前,由.NET自动调用。
(13)允许有非自动实现的实例属性 注1:自动实现的属性特点 -- get或set中没有内容,编译时由编译器生成同名的private后备字段 注2:属性可以理解为逻辑简单的成员函数,不占用对象size
(14)允许有自动实现和非自动实现的静态属性
(15)支持运算符重载(注:运算符重载必须声明为public static)
(16)结构体成员函数不能virtual,仅能使用override重写从System.ValueType继承的方法
(17)从接口继承时,必须实现所有接口函数,有两种实现方式:显示实现和隐式实现,两种方式中至少有一个实现接口,也可同时存在
当仅显式实现接口时,实现的成员不能通过类实例访问,只能通过接口实例访问。
当仅隐式实现接口时(需加上public),实现的成员可以通过类实例访问,也可以通过接口实例访问。
当同时显式和隐式实现接口时,类实例调用隐式实现的接口,接口实例调用显式实现的接口。
interface SI { int Add(int a, int b); int Sub(int a, int b); long Mul(int a, int b); } struct SA : SI { // 调用顺序 【1】 public const string Name = "SA"; public static string Name2; public readonly string Name3;//readonly字段只能在构造函数或者构造函数的初始化器中声明时赋值 public static readonly string Name4 = "SA4"; public string Name5; public int Id; bool Sex; private short Age; //protected float Scores; public int Attr1 { get { return 0; } } private int attr2; public int Attr2 { get { return attr2; } private set { attr2 = value; } } private int attr3; public int Attr3 { get { return attr3; } set { attr3 = value; } } //public int Attr4 { get; set; } //自动实现的属性,特点是get或set中没有内容 internal float Length; //protected internal double Weight; static short Sm1; static string Sm2; private static short Sm3 = 100; //protected static float Sm4; internal static float Sm5; //protected internal static double Sm6; // 静态属性 public static int SAttr1 { get { return 0; } } private static int sattr2; public static int SAttr2 { get { return sattr2; } private set { sattr2 = value; } } private static int sattr3; public static int SAttr3 { get { return sattr3; } set { sattr3 = value; } } public static int SAttr4 { get; set; } // 支持静态的自动实现属性 // 调用顺序 【3】 public SA(string s) { Name3 = s; Name5 = "SA5"; Id = 6; Sex = false; Age = 10; //Attr2 = 35; // 属性必须在所有字段初始化后才能设置 attr2 = 30; attr3 = 35; Length = 8.0f; //Attr1 = 20; // 只读型属性不能被赋值 Attr2 = 45; Attr3 = 50; } // 调用顺序 【3】 public SA(string s, int n) : this(s) { Name3 = "Name3"; Id = 7; } // 调用顺序 【2】 static SA() { SAttr2 = 10; SAttr3 = 20; SAttr4 = 30; //Name = "static SA";//const常量只能在初始化器中声明时初始化 Name2 = "static SA2"; //Name3 = "test";//非static变量 Name4 = "hello"; } void Test() { } //protected void Test2() { } public void Test3() { } internal void Test4() { } //protected internal void Test5() { } static void STest() { } //protected static void STest2() { } public static void STest3() { } internal static void STest4() { } //protected internal static void STest5() { } // 运算符重载 public static SA operator +(SA a, SA b) { SA c = new SA(); c.Id = a.Id + b.Id; return c; } // 实现所有的SI接口 方式1和方式2至少有一个实现接口,也可同时存在 // 方式1:显示实现 int SI.Add(int a, int b) { Console.WriteLine("SI.Add"); return a + b; } //int SI.Sub(int a, int b) //{ // Console.WriteLine("SI.Sub"); // return a - b; //} long SI.Mul(int a, int b) { Console.WriteLine("SI.Mul"); return a * b; } // 方式2:隐式实现 //public int Add(int a, int b) //{ // Console.WriteLine("SA public Add"); // return a + b; //} public int Sub(int a, int b) { Console.WriteLine("SA public Sub"); return a - b; } public long Mul(int a, int b) { Console.WriteLine("SA public Mul"); return a * b; } } string ss = SA.Name2; SA sa = new SA("Hello"); sa.Test3(); //sa.Add(2, 1);//由于只实现了SI.Add,在SA的实例接口中对Add不可见 sa.Sub(2, 1);//打印:SA public Sub sa.Mul(2, 1);//打印:SA public Mul SI sai = sa as SI; if (sai != null) { sai.Add(2, 1);//打印:SI.Add sai.Sub(2, 1);//打印:SA public Sub sai.Mul(2, 1);//打印:SI.Mul } SA sa2; SA sa3 = new SA(); SA sa4 = new SA("Hi", 12) { // 调用顺序 【4】 // 对象初始化器(需.Net 3.0及以上),在其中可以对public、internal类型的非static成员变量及可写属性进行赋值 Name5 = "Name5", Attr3 = 60, Id = 100, Length = 3.14f, };
类 -- 引用类型
访问修饰符 修饰符 class 类名称 : 父类名称, 接口名称1, 接口名称2
(1)单实现继承,多接口继承
(2)允许有析构函数(终结器finalizer)
(3)允许显示给出无参构造函数和重载多个带参数的构造函数
(4)若没有提供构造函数,编译器会默认生成无参构造函数;若编写了带参数的构造函数,编辑器就不再默认生成无参构造函数了
(5)可直接在后面给任何字段、readonly字段、static字段进行赋值
(6)通过new在托管堆上分配对象空间,并调用类的构造函数进行初始化,如:CA o = new CA() 或 CA o = new CA("Hello");
(7)允许有静态构造函数,但不允许带访问修饰符
(8)静态构造函数只会被执行一次,也就是在创建第一个实例或引用任何静态成员之前,由.NET自动调用。
(9)允许有自动实现的属性和非自动实现的属性 注1:自动实现的属性特点 -- get或set中没有内容,编译时由编译器生成同名的private后备字段 注2:属性可以理解为逻辑简单的成员函数,不占用对象size
(10)支持运算符重载(注:运算符重载必须声明为public static)
(11)除了构造函数外,成员函数和属性默认不是virtual
class CA { // 调用顺序 【1】 public const string Name = "CA"; //readonly字段是一个实例字段,类的每个实例可以有不同的值 //readonly字段只能在后面直接赋值或在构造函数或者构造函数的初始化器中声明时赋值 internal readonly string Name2 = "CA2"; internal protected readonly string Name3; public int Id = 10; bool Sex; private short Age; protected float Scores; public static string Name4 = "CA4"; static int Sm1; private static bool Sm2 = true; internal static float Sm3 = 3.0f; protected static short Sm4 = 10; protected internal static double Sm5; static readonly string Name5; // 属性的get与set访问器访问权限设置规则 // 1. 在get与set访问器中,必须有一个具备属性的访问级别(即:前面不设置权限) // 2. get与set访问器设置访问权限必须要低于属性的访问级别(public > protected internal > internal > protected > private) // 自动实现的属性,编译时由编译器生成同名的private后备字段 // 自动实现的属性,必须要同时写get与set访问器 public int Attr1 { get; set; } public int Attr2 { get; protected internal set; } protected int Attr3 { private get; set; } private int attr4; public int Attr4 { get { return attr4; } set { attr4 = value; } } public int Attr5 { get { return 100; } set { int a = value; } } private int attr6 = 2; public int Attr6//只读属性 { get { return attr6; } } private int attr7; public int Attr7//只写属性 这是不好的编程方式,最好使用方法来替代 { set{ attr7 = value; } } // 调用顺序 【3】 //自定义了构造函数,编译器就不会生成缺省无参构造函数 public CA(string s1, int a1) { this.Attr1 = a1; this.Name2 = s1; } // 调用顺序 【3】 //构造函数初始化器 在之前先调用CA(string s1, int a1)构造函数 //除了调用当前类的构造函数外,也可以调用基类的构造函数(将this改成base) //构造函数初始化器只能有一个调用 public CA(string s1) : this(s1, 10) { } // 调用顺序 【3】 public CA() { this.Attr1 = 10; Attr2 = 20; this.Name2 = "CC2"; Name3 = "CA3"; } // 调用顺序 【2】 static CA() { //Name = "Test"; //const常量只能在初始化器中声明时初始化 Sm5 = 20.5; Name5 = "Hi"; } //public ~CA() //{ //} ~CA() { Name4 = null; } // 该析构函数编译后生成如下逻辑 // 当对象被执行GC时,GC会调用Finalize函数进行最后的清理工作 //protected override void Finalize() //{ // try // { // Name4 = null; // } // finally // { // base.Finalize(); // } //} void Test() { } void Test(int a) { }//方法重载 protected virtual void Test2() { } public virtual void Test3() { } internal void Test4() { } protected internal void Test5() { } static void STest() { } protected static void STest2() { } public static void STest3() { } internal static void STest4() { } protected internal static void STest5() { } [System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "MessageBox", SetLastError = true, CharSet = System.Runtime.InteropServices.CharSet.Unicode)] public static extern int WinMessageBox(IntPtr hWnd, String text, String caption, uint type); // 运算符重载 public static CA operator +(CA a, CA b) { CA c = new CA(); c.Id = a.Id + b.Id; return c; } } CA ca = new CA(); ca.Test3(); CA ca2 = new CA("Go", 100); ca2.Test4(); CA ca3 = new CA("Hi") { // 调用顺序 【4】 // 对象初始化器(需.Net 3.0及以上),在其中可以对public、internal类型的非static成员变量及可写属性进行赋值 Id = 12, Attr1 = 51, Attr2 = 52, Attr4 = 53, Attr7 = 58 }; ca3.Test5(); CA.WinMessageBox(IntPtr.Zero, "Hello World!", "title", 0); // 弹出MessageBox对话框
修饰符:
sealed 密封类,前类不能被继承
注:sealed还可修饰函数(属性),使得当前虚函数(属性)不能被子类重写
partial 部分类,允许将类的成员放在不同文件中,在编译时将采用与的方式合并成一个完整的类
// Test1.cs partial class CA : CBase, IA { public int nValue1; public void Test1() { } } // Test2.cs sealed partial class CA : IB { public int nValue2; public void Test2() { } } // 等价于 sealed class CA : CBase, IA, IB { public int nValue1; public int nValue2; public void Test1() { } public void Test2() { } }
static 静态类,不能被实例化且只能包含非protected的静态字段、静态函数和静态属性
static class CSA { public const string Name = "CSA"; private static int nValue1; //protected static int nValue2; // 静态类中不包含protected类型的成员变量 //internal protected static int nValue3; internal static int nAttr1 { get; set; } // 静态构造函数 static CSA() { nValue1 = 100; } static public void Test1() { } }
abstract 抽象类,不能被实例化
1、抽象类不能被sealed,也不能用于实例化对象
2、抽象类中可以有实现的函数(属性)
3、抽象类可以包含非抽象类含有的各种变量、函数和属性等,也有可以从非抽象类、抽象类和接口派生
4、抽象类从接口派生时,若不想实现接口中的成员,则可把其成员声明为abstract
5、抽象函数(属性)不能是static或private的,只能在抽象类中声明
6、抽象函数(属性)是隐式虚函数,无需用virtual关键字修饰
7、抽象函数(属性)只有声明没有实现,需在非抽象的派生类中重写所有的抽象函数(属性)
8、抽象函数(属性)不能在派生类用base关键字进行访问
abstract class CA { public void Test1() { Console.WriteLine("CA Test1"); } } abstract class CB : CA { public abstract void Test2(); public abstract int Attr1 { get; set; } public abstract int Attr2 { get; } } class CC : CB { public override void Test2() { Console.WriteLine("CC Test2"); } private int attr1; public override int Attr1 { get { return attr1; } set { attr1 = value; } } public override int Attr2 { get { return 0; } } } CC c = new CC(); c.Test1();// 打印出 CA Test1 c.Test2();// 打印出 CC Test2 c.Attr1 = 100; // c.attr1=100 int n = c.Attr2; // n=0
继承
1.通过base可以调用基类中的方法和属性
成员修饰符
sealed 修饰函数(属性),使得当前虚函数(属性)不能被子类重写
abstract 在抽象类用于修饰函数(属性),只有声明没有实现,需在非抽象的派生类中重写
virtual/override 修饰函数(属性),不能是private的,可在派生类中使用override修饰后重写
new 可在派生类中使用new修饰相同的函数(属性)来隐藏继承的成员(无论是否为virtual,都可以隐藏)
extern 仅静态DllImport方法,实际的方法在外部用其他语言实现
interface CI { int Add(int a, int b); int Sub(int a, int b); long Mul(int a, int b); } class CA { string Name; protected int Id; int attr1 = 1; internal int Attr1 { get { Console.WriteLine("CA get Attr1."); return attr1; } } int attr2 = 2; public int Attr2 { get { Console.WriteLine("CA get Attr2."); return attr2; } } protected virtual int Attr3 //虚属性 { get { Console.WriteLine("CA get Attr3."); return 0; } } protected int attr4; public virtual int Attr4 //虚属性 { get { Console.WriteLine("CA get Attr4."); return attr4; } set { attr4 = value; } } public CA() : this("Name1", 10)//调用CA(string s1, int n) { } public CA(string s1, int n) { this.Name = s1; this.Id = n; } ~CA() { Name = null; } internal void Test1() { Console.WriteLine("CA Test1."); } public virtual void Test2() { Console.WriteLine("CA Test2."); } public virtual void Test3() { Console.WriteLine("CA Test3."); } } class CB : CA, CI { private new int Attr1 //覆盖基类CA的属性 { get { Console.WriteLine("CB get Attr1."); return 0; } } public new int Attr2 { get { Console.WriteLine("CB get Attr2."); return 0; } } public new int Attr3 //覆盖基类CA的虚属性 { get { Console.WriteLine("CB get Attr3."); return 0; } } public override int Attr4 //重写基类CA的虚属性 { get { Console.WriteLine("CB get Attr4."); return attr4; } set { Console.WriteLine("CB set Attr4."); attr4 = value; } } public CB() : this("Name1", 10)//调用CB(string s1, int n) { } public CB(string s1, int n) : base(s1, n)//调用CA(string s1, int n) { } private new void Test1() { Console.WriteLine("CB Test1."); } public new void Test2() { Console.WriteLine("CB Test2."); } public override void Test3() { Console.WriteLine("CB Test3."); } public void Test4() { Console.WriteLine("CB Test4.");//打印CB Test4. base.Test1();//打印CA Test1. base.Test2();//打印CA Test2. base.Test3();//打印CA Test3. } // 实现所有的CI接口 方式1和方式2至少有一个实现接口,也可同时存在 // 方式1:显示实现 int CI.Add(int a, int b) { Console.WriteLine("CI.Add"); return a + b; } //int CI.Sub(int a, int b) //{ // Console.WriteLine("CI.Sub"); // return a - b; //} long CI.Mul(int a, int b) { Console.WriteLine("CI.Mul"); return a * b; } // 方式2:隐式实现 //public int Add(int a, int b) //{ // Console.WriteLine("CB public Add"); // return a + b; //} public int Sub(int a, int b) { Console.WriteLine("CB public Sub"); return a - b; } public long Mul(int a, int b) { Console.WriteLine("CB public Mul"); return a * b; } } CA o = new CB(); // Attr4为虚属性,且被派生类CB使用override重写 o.Attr4 = 100;//打印CB set Attr4. // Attr1被派生类CB使用new隐藏 int na1 = o.Attr1;//打印CA get Attr1. // Attr2被派生类CB使用new隐藏 int na2 = o.Attr2;//打印CA get Attr2. // Attr4为虚属性,且被派生类CB使用override重写 int na4 = o.Attr4;//打印CB get Attr4. // Test1函数被派生类CB使用new隐藏 o.Test1();//打印CA Test1. // Test2虚函数被派生类CB使用new隐藏 o.Test2();//打印CA Test2. // Test3虚函数被派生类CB使用override重写 o.Test3();//打印CB Test3. CB ob = o as CB; // Attr1被派生类CB使用new隐藏,并设置为private int nb1 = ob.Attr1;//打印CA get Attr1. // Attr2被派生类CB使用new隐藏 int nb2 = ob.Attr2;//打印CB get Attr2. // Attr3为protected虚属性,被派生类CB使用new隐藏,并设置为public int nb3 = ob.Attr3;//打印CB get Attr3. // Attr4为虚属性,且被派生类CB使用override重写 int nb4 = ob.Attr4;//打印CB get Attr4. // Test1函数被派生类CB使用new隐藏,并设置为private ob.Test1();//打印CA Test1. // Test2虚函数被派生类CB使用new隐藏 ob.Test2();//打印CB Test2. // Test3虚函数被派生类CB使用override重写 ob.Test3();//打印CB Test3. // Test4函数为派生类CB的函数 ob.Test4(); //ob.Add(2, 1);//由于只实现了CI.Add,在CB的实例接口中对Add不可见 ob.Sub(2, 1);//打印:CB public Sub //同时实现了CI.Mul和CB类的Mul函数,在CB的实例接口将调用CB类的Mul函数 ob.Mul(2, 1);//打印:CB public Mul CI cob = ob as CI; if (cob != null) { cob.Add(2, 1);//打印:CI.Add //由于没有实现CI.Sub,调用CB类的Sub函数 cob.Sub(2, 1);//打印:CB public Sub //同时实现了CI.Mul和CB类的Mul函数,CI接口将调用CI.Mul cob.Mul(2, 1);//打印:CI.Mul }
内部类(嵌套类)
1、使用内部类很大意义上是为了隐藏其外部类的实现
2、内部类默认访问修饰符为private,外部类默认访问修饰符为internal。内部类访问修饰符为private时,仅能在包含其的外部类中使用
3、内部类和其外部类并没有太多关联,在外部使用内部类时,首先需要将内部类的访问修饰符设置为internal、internal protected或public,另外还要带上外部类名前缀(像命名空间一样)
4、内部类拿到外部类的引用后,使用该引用可访问外部类所有成员(含private和protected)
5、与普通类一样,嵌套类也可以被继承
class COuter { private int nValue1; private int Attr1 { get; set; } private void Test() { } private static void STest() { } public virtual void PubTest() { CInner cci = new CInner(this); cci.Func(); } protected class CInner { private COuter outer; public CInner(COuter o) { this.outer = o; } public virtual void Func() { outer.nValue1 = 100;// 内部类直接修改private字段 outer.Attr1 = 200;// 内部类直接修改private属性 outer.Test();// 内部类直接访问private方法 COuter.STest();// 内部类直接访问private静态方法 } } } class COuterEx : COuter { public override void PubTest() { CInnerEx cci = new CInnerEx(this); cci.Func(); } protected class CInnerEx : CInner { public CInnerEx(COuterEx o):base(o) { } public override void Func() { base.Func(); Console.WriteLine("CInnerEx Func!"); } } } COuter cox = new COuterEx(); cox.PubTest();
接口
(1)与抽象类一样,不能直接实例化接口
(2)接口中只能包含成员函数(属性、索引器、事件),不能包含常量、成员变量、构造函数、析构函数、运算符、类型转换函数、及所有静态成员。
(3)接口成员是自动公开的,无需用public再做修饰
(4)接口可从多个接口继承,但不能从类继承
(5)值类型和引用类型都可以从一个或多个接口派生
(6)接口中的函数(属性、索引器、事件)只有声明没有实现,需在实现类中实现所有的函数(属性、索引器、事件)
(7)接口用于规范,抽象类用于共性 注:抽象类可提供某些方法的实现
(8)从接口继承时,必须实现所有接口函数,有两种实现方式:显示实现和隐式实现,两种方式中至少有一个实现接口,也可同时存在(详见上文结构体和类-继承中的示例)
当仅显式实现接口时,实现的成员不能通过类实例访问,只能通过接口实例访问。
当仅隐式实现接口时(需加上public),实现的成员可以通过类实例访问,也可以通过接口实例访问。
当同时显式和隐式实现接口时,类实例调用隐式实现的接口,接口实例调用显式实现的接口。
(9)接口IB从接口IA派生时:增加一个在IA中同名接口函数Test,需要在接口函数Test前加上new关键字,告诉编译器符号修饰时带上接口名前缀以作区分
实现类从IB派生时,要同时实现IA.Test和IB.Test,如同两个不同名称的接口函数一样
interface IA { void Test(); void Test1(); void Test2(); } interface IB : IA { new void Test();// 基类IA中有一个同名Test接口函数,因此需要用new进行修饰 new void Test1();// 同上 void Test3(); void Test4(); void Test5(); } abstract class CC : IB { // Only显示实现:IA中的Test函数接口 void IA.Test() { Console.WriteLine("IA.Test"); } // Only显示实现:IB中的Test函数接口 void IB.Test() { Console.WriteLine("IB.Test"); } // only隐式虚实现:IA和IB中的Test1函数接口 public virtual void Test1() { Console.WriteLine("CC Test1"); } // 同时显示和隐式实现:IA中的Test2函数接口 void IA.Test2() { Console.WriteLine("IA.Test2"); } public void Test2() { Console.WriteLine("CC Test2"); } // 同时显示和隐式虚实现:IB中的Test3函数接口 void IB.Test3() { Console.WriteLine("IB.Test3"); } public virtual void Test3() { Console.WriteLine("CC Test3"); } // only隐式虚实现:IB中的Test4函数接口 public virtual void Test4() { Console.WriteLine("CC Test4"); } // only隐式虚实现:IB中的Test4函数接口 -- 使用抽象函数的方式 public abstract void Test5(); } class CD : CC { public new void Test1() // 使用new隐藏 { Console.WriteLine("DD Test1"); } public new void Test2() // 使用new隐藏 { Console.WriteLine("DD Test2"); } protected new void Test4() // 使用new隐藏,并降低Test4可见权限为protected { Console.WriteLine("DD Test4"); } public override void Test5() // 使用override重载 { Console.WriteLine("DD Test5"); } } CD od = new CD(); //od.Test(); // Test函数在CD中不可见 od.Test1();//打印DD Test1 od.Test2();//打印DD Test2 od.Test3();//打印CC Test3 od.Test4();//打印CC Test4 od.Test5();//打印DD Test5 CC oc = od as CC; //oc.Test(); // Test函数在CC中不可见 oc.Test1();//打印CC Test1 oc.Test2();//打印CC Test2 oc.Test3();//打印CC Test3 oc.Test4();//打印CC Test4 oc.Test5();//打印DD Test5 IA ia = od as IA; ia.Test();//打印IA.Test ia.Test1();//打印CC Test1 ia.Test2();//打印IA.Test2 IB ib = od as IB; ib.Test();//打印IB.Test ib.Test1();//打印CC Test1 ib.Test2();//打印IA.Test2 ib.Test3();//打印IB.Test3 ib.Test4();//打印CC Test4 ib.Test5();//打印DD Test5
.Net Framework中常用的接口有IDisposable、迭代器【IEnumerator、IEnumerable】、泛型版本迭代器【IEnumerator<T>、IEnumerable<T>】
// Disposable接口,通过显示调用Dispose接口来进行清理工作 public interface IDisposable { void Dispose (); } // 迭代器 public interface IEnumerator { object Current { get; } bool MoveNext (); void Reset (); } public interface IEnumerable { IEnumerator GetEnumerator(); } // 泛型版本的迭代器 public interface IEnumerator<out T> : IDisposable, IEnumerator { T Current { get; } } public interface IEnumerable<out T> : IEnumerable { new IEnumerator<T> GetEnumerator(); }
委托
(1)委托的功能类似于c/c++里面的函数指针,但委托更安全,它会在编译器期检查函数的参数和返回值类型
(2)委托本质是一个类,在使用前需要声明
静态数组
用方括号声明静态数组编译成字节码后会创建一个派生于抽象基类Array的新类
static int[] ArraySum(int[] array1, int[] array2) { int[] result = new int[5]; for (int i = 0; i < 5; i++) { result[i] = array1[i] + array2[i]; } return result; } static int Array1PartSum(ArraySegment<int> array1) { int result = 0; for (int i = array1.Offset; i < array1.Offset+array1.Count; i++) { result += array1.Array[i]; } return result; } public class Person { public string FirstName, LastName; } int[] array1 = null; int[] array2 = new int[3]; int[] array3 = new int[3] { 1, 2, 3 }; int[] array4 = new int[] {1, 2, 3, 4, 5}; int[] array5 = { 1, 2, 3, 4, 5 }; int nLengthOfArray5 = array5.Length; // 5 int nArray5Sum = 0; foreach (int an in array5) // 继承了IEnumerable接口,必须实现自己的IEnumerator GetEnumerator()方法和对应IEnumerator的数据类型 { nArray5Sum += an; // nArray5Sum = 1+2+3+4+5 = 15 } Person[] Persons1 = new Person[2]; Persons1[0] = new Person { FirstName = "Bruce", LastName = "Lee" }; Persons1[1] = new Person { FirstName = "Jack", LastName = "Wong" }; Person[] Persons2 = { new Person { FirstName = "Jim", LastName = "Green" }, new Person { FirstName = "Kate", LastName = "Green" }, new Person { FirstName = "Lei", LastName = "Li" } }; //2x2的二维数组 // det1 = | 1 2 | // | 3 4 | // int[,] det1 = new int[2,2]; det1[0,0] = 1; det1[0,1] = 2; det1[1,0] = 3; det1[1,1] = 4; int nLengthOfdet1 = det1.Length; // 4 long nLongLengthOfdet1 = det1.LongLength; // 4 个数>2147483647时,应使用LongLength获取数组长度 int nRankOfdet1 = det1.Rank; // 2 数组的维度 // 3x3的二维数组 int[,] det2 = { {1,2,3}, {4,5,6}, {7,8,9} }; int edet2 = det2[2, 1]; //edet2=8 int nLengthOfdet2 = det2.Length; // 9 // 3x2x1的三维数组 int[,,] det3 = { {{1},{2}}, {{3},{4}}, {{5},{6}} }; int edet3 = det3[1, 1, 0]; //edet3=4 int nLengthOfdet3 = det3.Length; // 6 // 锯齿数组 // jagged = | 1 2 | // | 3 4 5 6 7 8 | // | 9 10 11 | int[][] jagged = new int[3][]; jagged[0] = new int[]{1, 2}; int [] ar1 = { 3, 4, 5, 6, 7, 8 }; jagged[1] = ar1; jagged[2] = new int[3] { 9, 10, 11 }; int nLengthOfjagged = jagged.Length; // 3 // 数组调用和返回值 int[] sumarray = ArraySum(new int[5] { 1, 2, 3, 4, 5 }, new int[5] { 10, 5, 8, 3, 12 });// sum = {11,7,11,7,17} // 计算sumarray数组中从索引为1起后面3个元素之和 int sum = Array1PartSum(new ArraySegment<int>(sumarray, 1, 3)); // 7+11+7=25
Array类
Array intArray1 = Array.CreateInstance(typeof(int), 5);// 创建元素个数为5的一维数组 for (int i = 0; i < intArray1.Length; i++) { intArray1.SetValue(100, i);// 设置intArray1的每个元素为100 } for (int i = 0; i < intArray1.Length; i++) { Console.WriteLine(intArray1.GetValue(i)); } int[] intArray2 = (int[])intArray1; int[] intArray3 = (int[])intArray1.Clone();// 创建浅表副本(拷贝后,引用类型成员变量仍然指向同一块内存区域) int[] intArray4 = new int[intArray1.Length]; Array.Copy(intArray1, intArray4, intArray1.Length);// 创建浅表副本(拷贝后,引用类型成员变量仍然指向同一块内存区域) Array floatArray1 = Array.CreateInstance(typeof(float), 2, 3); // 创建一个起始索引为[3]长度为2的一维数组 Array floatArray2 = Array.CreateInstance(typeof(float), new int[] { 2, 3 }); // 创建一个起始索引为[0][0]的2x3的二维数组 Array floatArray3 = Array.CreateInstance(typeof(float), new int[] { 2, 3 }, new int[] { 1, 2 }); // 创建一个起始索引为[1][2]的2x3的二维数组
排序
1. 由于基本类型、String实现了IComparable接口,因此可以直接使用Array的Sort静态函数来排序
2. 结构体、类须实现IComparable接口来支持Sort函数进行排序
3. Array的Sort有多个重载函数,static Void Sort<T>(T[] array, System.Comparison<T> comparison)允许传入派生于IComparer<T>的对象来自定义排序行为
public struct Color : IComparable<Color> { public float R; public float G; public float B; public float A; public int CompareTo(Color other)//按照R、G、B、A的顺序升序排列 { if (this.R > other.R) return 1; if (this.R < other.R) return -1;//小于则交换 if (this.G > other.G) return 1; if (this.G < other.G) return -1; if (this.B > other.B) return 1; if (this.B < other.B) return -1; if (this.A> other.A) return 1; if (this.A < other.A) return -1; return 0; } } public class ColorComparer : IComparer<Color> { public int Compare(Color c1, Color c2)//按照R、G、B、A的顺序降序排列 { if (c1.R > c2.R) return -1;//大于则交换 if (c1.R < c2.R) return 1; if (c1.G > c2.G) return -1; if (c1.G < c2.G) return 1; if (c1.B > c2.B) return -1; if (c1.B < c2.B) return 1; if (c1.A > c2.A) return -1; if (c1.A < c2.A) return 1; return 0; } } String[] strArray = {"tencent", "Map", "Hello", "Test", "apple"}; Array.Sort(strArray, 1, 3); // 对数组strArray中从索引为1起的后3个元素进行排序 {"tencent", "Hello", "Map", "Test", "apple"} Color[] ClrArray = new Color[3]; ClrArray[0].R = 100; ClrArray[1].R = 100; ClrArray[1].G = 100; ClrArray[2].R = 80; ClrArray[2].G = 120; Array.Sort(ClrArray);//自定义类型结构体Color需要从IComparable<Color>接口派生,实现public int CompareTo(Color other)虚方法;否则会抛InvalidOperationException异常 Array.Sort(ClrArray, new ColorComparer());//使用ColorComparer(需从IComparer<Color>接口派生)实例中的int Compare(Color c1, Color c2)方法进行排序
元组(Tuple) 需.Net 4.0及以上
数组合并了相同类型的对象,而元组合并了不同类型的对象
元组默认支持1~7种类型,当类型大于7种时,需在第8个参数中传入一个元组类型,通过嵌套来支持更多类型
Tuple<int, string> t0 = Tuple.Create<int, string>(18, "James"); Tuple<int, int, string> t1 = new Tuple<int, int, string>(1, 15, "Rose"); Tuple<bool, int, int, int, string, string, string> t2 = new Tuple<bool, int, int, int, string, string, string>(true, 1, 2, 3, "How", "are", "you"); Tuple<bool, int, int, int, string, string, string, Tuple<int, int>> t3 = new Tuple<bool, int, int, int, string, string, string, Tuple<int, int>>(true, 1, 2, 3, "How", "are", "you", new Tuple<int, int>(10, 20)); Console.WriteLine("Age:{0} Name:{1}", t0.Item1, t0.Item2); // Age:18 Name:James Console.WriteLine("Sex:{0} Code:{1}", t3.Item1, t3.Rest.Item2); // Sex:True Code:20
数组与元组内容相等性比较 需.Net 4.0及以上
数组与元组都实现了IStructuralEquatable接口,该接口是.Net4.0新增的,用于比较两个数组元组是否有相同的内容
struct Student { public int Id; public string Name; public bool Sex; public Student(int InId, string InName, bool InSex) { Id = InId; Name = InName; Sex = InSex; } } class StdArrayEqualityComparer : EqualityComparer<Student> { public override bool Equals(Student x, Student y) { return (x.Id == y.Id) && (x.Name == y.Name) && (x.Sex == y.Sex); } public override int GetHashCode(Student obj) { int code = obj.GetHashCode(); int code1 = obj.Id.GetHashCode(); int code2 = obj.Name.GetHashCode(); int code3 = obj.Sex.GetHashCode(); if (code != code1) { code ^= code1;// 进行异或操作 } if (code != code2) { code ^= code2; } if (code != code3) { code ^= code3; } return code; } } class StdTupleEqualityComparer : IEqualityComparer { public new bool Equals(object x, object y)// new用于显式地表明隐藏基类中的同名方法(object中定义了带两个参数的静态Equals方法) { return x.Equals(y); } public int GetHashCode(object obj) { return obj.GetHashCode(); } } Student[] StArray1 = { new Student(0, "Jim", true), new Student(1, "Kate", false) }; Student[] StArray2 = { new Student(0, "Jim", true), new Student(1, "Kate", false) }; if (StArray1 == StArray2) { Console.WriteLine("StArray1 == StArray2"); // false } if (StArray1.Equals(StArray2)) { Console.WriteLine("StArray1 Equals StArray2"); // false } if ((StArray1 as IStructuralEquatable).Equals(StArray2))// IStructuralEquatable重写过object的Equals函数,会调用object的Equals函数 { Console.WriteLine("StArray1 IStructuralEquatable Equals StArray2"); // false } if ((StArray1 as IStructuralEquatable).Equals(StArray2, EqualityComparer<Student>.Default))// IStructuralEquatable新增的Equals函数接口,传入缺省比较对象实例 { Console.WriteLine("StArray1 IStructuralEquatable Equals Default StArray2"); // true } if ((StArray1 as IStructuralEquatable).Equals(StArray2, new StdArrayEqualityComparer()))// IStructuralEquatable新增的Equals函数接口,传入自定义比较对象实例 { Console.WriteLine("StArray1 StdArrayEqualityComparer StArray2"); // true } Tuple<int, Student> StTp1 = new Tuple<int, Student>(100, new Student(0, "Jim", true)); Tuple<int, Student> StTp2 = new Tuple<int, Student>(100, new Student(0, "Jim", true)); if (StTp1 == StTp2) { Console.WriteLine("StTp1 == StTp2"); // false } if (StTp1.Equals(StTp2))// Tuple重写过object的Equals函数,使得其进行值比较 { Console.WriteLine("StTp1 Equals StTp2"); // true } if ((StTp1 as IStructuralEquatable).Equals(StTp2)) // IStructuralEquatable重写过object的Equals函数,会调用object的Equals函数 { Console.WriteLine("StTp1 IStructuralEquatable Equals StTp2"); // true } if ((StTp1 as IStructuralEquatable).Equals(StTp2, EqualityComparer<object>.Default))// IStructuralEquatable新增的Equals函数接口,传入缺省比较对象实例 { Console.WriteLine("StTp1 IStructuralEquatable Equals Default StTp2"); // true } if ((StTp1 as IStructuralEquatable).Equals(StTp2, new StdTupleEqualityComparer()))// IStructuralEquatable新增的Equals函数接口,传入自定义比较对象实例 { Console.WriteLine("StTp1 StdTupleEqualityComparer StTp2"); // true }
集合
注1:虚线框的为接口、红色实线框的为功能类
注2:左边为非泛型集合类、右边为泛型集合类
.Net Framework1.0包含非泛型集合类,如:ArrayList、Hashtable、Queue、Stack、SortedList等
.Net Framework2.0添加了泛型和泛型集合类的支持,如:List<T>、Dictionary<TKey,TValue>、Queue<T>、Stack<T>、SortedList<TKey,TValue>等
大多数集合类都在System.Collections和System.Collections.Generic名称空间中。非泛型集合类在System.Collections中,泛型集合类在System.Collections.Generic中。
专用于特定类型的非泛型集合类在System.Collections.Specialized中,线程安全的泛型集合类在System.Collections.Concurrent中。
ArrayList、List<T>
ArrayList是可变长数组,你可以将任意多的数据Add到ArrayList里面。其内部维护的数组,当长度不足时,会自动扩容为原来的两倍。
但是ArrayList也有一个缺点,就是存入ArrayList里面的数据都是Object类型的,所以如果将值类型存入和取出的时候会发生装箱、拆箱操作,这会影响程序性能。在.Net 2.0泛型出现以后,就提供了List<T>。
List<T>是ArrayList的泛型版本,它不再需要装箱拆箱,直接取,直接用,它基本与ArrayList一致,不过在使用的时候要先设置好它的类型,而设置好类型之后,不是这种类型的数据,是不允许Add进去的。
就性能来说,如果要存进数组的只有一种数据,那么无疑List<T>是最优选择。
List<int> ListInt = new List<int>();
如果一个变长数组,又要存int,又要存string。那么就只能用ArrayList。
StringCollection
字符串类型的可变长数组
Hashtable、Dictionary<TKey,TValue>、ConcurrentDictionary<TKey,TValue>
Hashtable(哈希表)是一种根据key查找非常快的键值数据结构,不能有重复key,而且由于其特点,其长度总是一个素数,所以扩容后容量会比2倍大一点点,加载因子为0.72f。
当要大量使用key来查找value的时候,HashTable无疑是最佳选择,HashTable与ArrayList一样,是非泛型的,value存进去是object,存取会发生装箱、拆箱,所以出现了Dictionary<TKey,TValue>。
Dictionary<TKey,TValue>是HashTable的泛型版本,存取同样快,但是不需要装箱和拆箱了。而且,其优化了算法,Hashtable是0.72,它的浪费容量少了很多。
Dictionary<string,Person> Dic = new Dictionary<string,Person>();
Hashtable允许单线程写入多线程读取,对Hashtable进一步调用Synchronized()方法可以获得完全线程安全的类型,而Dictionary非线程安全, 必须人为使用lock语句进行保护, 效率大减.
如果 HashTable要允许并发读但只能一个线程写, 要这么创建 HashTable实例:
System.Collections.Hashtable htSyn = System.Collections.Hashtable.Synchronized(new System.Collections.Hashtable());
Hashtable也可以使用lock语句进行保护,但要lock的不是HashTable,而是其成员变量SyncRoot
private static System.Collections.Hashtable htCache = new System.Collections.Hashtable(); public static void AccessCache () { lock ( htCache.SyncRoot ) { htCache.Add ( "key", "value" ); } } //也可以用Monitor.Enter和Exit函数达到与lock相同的结果 public static void AccessCache () { System.Threading.Monitor.Enter ( htCache.SyncRoot ); try { htCache.Add ( "key", "value" ); } finally { System.Threading.Monitor.Exit ( htCache.SyncRoot ); } }
ConcurrentDictionary<TKey,TValue>为线程安全的Dictionary<TKey,TValue>
注:StringDictionary: 字符串类型的哈希表
HashSet<T>、SortedSet<T>
算法,存储结构都与哈希表相同,主要是设计用来做高性能集运算的,例如对两个集合求交集、并集、差集等。
HashSet<T>:集合中包含一组不重复出现且无特定顺序的元素。 .net 3.5加入
SortedSet<T>:集合中包含一组不重复出现且有序的元素。加入元素后,会自动排序 .net 4加入
Queue、Queue<T>、ConcurrentQueue<T>
Queue队列,Queue<T>泛型队列,ConcurrentQueue<T>线程安全版本泛型队列 先进先出。enqueue方法入队列,dequeue方法出队列。
Stack、Stack<T>、ConcurrentStack<T>
Stack栈,Stack<T>泛型栈,ConcurrentStack<T>线程安全版本泛型栈 先进后出。push方法入栈,pop方法出栈。
SortedList、SortedList<TKey,TValue>
集合中的数据是有序的。可以通过key来匹配数据,也可以通过int下标来获取数据。
添加操作比ArrayList,Hashtable略慢;查找、删除操作比ArrayList快,比Hashtable慢。
SortedDictionary<TKey,TValue>
SortedDictionary<TKey,TValue>相比于SortedList<TKey,TValue>其性能优化了,SortedList<TKey,TValue>其内部维护的是数组,
而SortedDictionary<TKey,TValue>内部维护的是红黑树(平衡二叉树)的一种,因此其占用的内存,性能都好于SortedDictionary<TKey,TValue>。唯一差在不能用下标取值。
ListDictionary、LinkedList<T>
ArrayList、List<T>、Hashtable等容器类,其内部维护的是数组Array来,ListDictionary和LinkedList<T>不用Array,而是用链表的形式来保存。链表最大的好处就是节约内存空间。
ListDictionary是单向链表。
LinkedList<T>双向链表。双向链表的优势,可以插入到任意位置。
HybridDictionary
HybridDictionary类充分利用了Hashtable查询效率高和ListDictionary占用内存空间少的优点,内置了Hashtable和ListDictionary两个容器,添加数据时内部逻辑如下:
当数据量小于8时,Hashtable为null,用ListDictionary保存数据。
当数据量大于8时,实例化Hashtable,数据转移到Hashtable中,然后将ListDictionary置为null。
BitArray、BitVector32
BitArray用于二进制运算,"或"、"非"、"与"、"异或非"等这种操作,只能存true或false;
BitVector32为结构体,速度非常快,只能存储32位,存在一个整数里。可使用section能选择片段的位置长度。