基本类型
字符、字符串和文本处理
字符
基本概念
-
基本结构
- .Net Framework中,字符总是表示成16位的Unicode代码值。
- 每个字符都是System.Char的实例,注意,该类型为值类型。
- Char.MaxValue='/uffff',表示65535。
Culture(字符或者字符串有文化的差异,需要注意)
-
转换(效率由高到低)
-
转型(强制类型转换)
Char a = (Int32)65;// A
-
使用Convert类型
Char a = Convert.ToChar(65); // A
-
使用IConvertible接口
Char a = (IConvertible(65)).ToChar(null); // A
-
-
思考这两种写法有什么不同?
// 思考:这两种写法有什么不同? char b = unchecked((Char)(65536*2 + 65)); Console.WriteLine(b); Char d = unchecked(Convert.ToChar(70000));// 这种是编译不过的 Console.WriteLine(d);
-
IL分析
// 有效的IL代码 .entrypoint // 代码大小 30 (0x1e) .maxstack 1 .locals init (char V_0, char V_1) IL_0000: nop IL_0001: ldc.i4.s 65 // 实际强制转换会减去65536的整数倍后再进行转换。 IL_0003: stloc.0 IL_0004: ldloc.0 IL_0005: call void [mscorlib]System.Console::WriteLine(char) IL_000a: nop IL_000b: ldc.i4 0x11170 // 已经超过Char.MaxValue IL_0010: call char [mscorlib]System.Convert::ToChar(int32) IL_0015: stloc.1 IL_0016: ldloc.1 IL_0017: call void [mscorlib]System.Console::WriteLine(char) IL_001c: nop IL_001d: ret
-
参考内容
字符串
String
基本概念
-
基本结构
- string表示一个不可变的顺序字符集(immutable)。
- String对象(它的字符数组)总是存在于堆上。
- 特别说明一点,struct上面的string也不例外,若是局部变量,在Stack上,若是引用类型的成员,则在Heap上。但是注意的是,当struct拷贝一个副本的时候,引用的堆控件也会重新分配初始化。
- 关于struct的说明,可以参考MSDN-Struct
- string不允许使用new关键字构造string对象。
- 字符串换行建议使用Environment.NewLine,而不是/r/n。
- 编程技巧:要在序号比较前更改字符串中的字符的大小写,应该使用String.ToUpperInvariant进行正规化(normalizing),微软对执行大写比较代码进行过优化。
-
语言文化(Culture)
- 后续补上,目前用到不多
-
字符串留用(string interning)
-
原理
1. 原理说明 CLR初始化的时候创建一个内部哈希表,在这个表中,键(key)是字符串,而值(value)是对托管堆中的string对象的引用,开始时候,哈希表为空。 暴露的API: - public static String Intern(String str); - public static String IsInterned(String str); 2. 影响CLR的特性 - CompilationRelaxationAttribute
-
字符串池
高效处理字符串
- StringBuilder
- ToString
- IFormatProvider
- Parse
安全字符串
- 后续整理
文本处理
编码(后续整理)
UTF-16(Unicode编码)
-
UTF-8
// 简单案例 public class EncodingTest { public static void Run() { string str = "hello,world"; // 获取utf8 var utf8Encoding = Encoding.UTF8; // 将字符串编译成字符串数组 var encodingBytes = utf8Encoding.GetBytes(str); // 显示编码好的值 Console.WriteLine(BitConverter.ToString(encodingBytes)); // 解码 var str2 = utf8Encoding.GetString(encodingBytes); Console.WriteLine(str2); Console.WriteLine("是否相等:" + Object.ReferenceEquals(str, str2)); } }
枚举类型和位标志
基本概念
枚举是值类型
-
使用枚举的理由
- 枚举类型使程序更容易编写、阅读和维护。
- 枚举是强类型的,不容易写错。
-
常见的用法
public class EnumTest { public static void Run() { // 强制转换 ColorEnum color = (ColorEnum)1; // Pink if(color == ColorEnum.Pink) { Console.WriteLine("强制转换成功"); } // 数字转换 int num = (int)color; // 1 Console.WriteLine("转换成数字:" + num); // 获取枚举项的名称 string name = Enum.GetName(typeof(ColorEnum), 1); // Pink Console.WriteLine("查找到的枚举值为1的名称:" + name); } } public enum ColorEnum { Orange, Pink }
位标志
-
普通枚举和位标志的区别
- 位标志为可以用来表示一组可以组合的枚举类型。
- 位标志表示位集合。
- 枚举值可以从0开始,按2的n-1次方表示,但是不一定是2的n次方。
- 可以用[Flags]特性标注位标志。
-
案例分析
public class Test { public static void Main(string[] args) { // 位标志 MyFlagEnum actions = MyFlagEnum.Close | MyFlagEnum.Open;// 增加,等于十进制的3,并装箱到actions指向的Heap中MyFlagEnum Console.WriteLine(actions.ToString("F")); // Close,Open MyFlagEnum myFlag = (MyFlagEnum)Enum.Parse(typeof(MyFlagEnum), "open", true); Console.WriteLine(myFlag.ToString("F"));// Open myFlag = (MyFlagEnum)Enum.Parse(typeof(MyFlagEnum), "2", false); Console.WriteLine(myFlag.ToString("F"));// Close myFlag = (MyFlagEnum)Enum.Parse(typeof(MyFlagEnum), "3", false); Console.WriteLine(myFlag.ToString("F")); // Open,Close myFlag = MyFlagEnum.All | MyFlagEnum.None; // 为什么这里只会出现ALL,从最大值计算减掉当前值,向下找组合,但是0不会给找到。 Console.WriteLine(myFlag.ToString("F"));// ALL } } [Flags] public enum MyFlagEnum { None = 0, Open = 0x0001, Close = 0x0002, All = 0x001F //十进制31 }
-
IL代码分析
.class public auto ansi beforefieldinit ILLearning.Test extends [mscorlib]System.Object { .method public hidebysig static void Main(string[] args) cil managed { .entrypoint // 代码大小 198 (0xc6) .maxstack 3 .locals init (valuetype ILLearning.MyFlagEnum V_0, valuetype ILLearning.MyFlagEnum V_1) IL_0000: nop IL_0001: ldc.i4.3 IL_0002: stloc.0 IL_0003: ldloc.0 IL_0004: box ILLearning.MyFlagEnum IL_0009: ldstr "F" IL_000e: call instance string [mscorlib]System.Enum::ToString(string) IL_0013: call void [mscorlib]System.Console::WriteLine(string) IL_0018: nop IL_0019: ldtoken ILLearning.MyFlagEnum IL_001e: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) IL_0023: ldstr "open" IL_0028: ldc.i4.1 // true,在IL中表示1,false表示0 IL_0029: call object [mscorlib]System.Enum::Parse(class [mscorlib]System.Type, string, bool) IL_002e: unbox.any ILLearning.MyFlagEnum IL_0033: stloc.1 IL_0034: ldloc.1 IL_0035: box ILLearning.MyFlagEnum IL_003a: ldstr "F" IL_003f: call instance string [mscorlib]System.Enum::ToString(string) IL_0044: call void [mscorlib]System.Console::WriteLine(string) IL_0049: nop IL_004a: ldtoken ILLearning.MyFlagEnum IL_004f: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) IL_0054: ldstr "2" IL_0059: ldc.i4.0 IL_005a: call object [mscorlib]System.Enum::Parse(class [mscorlib]System.Type, string, bool) IL_005f: unbox.any ILLearning.MyFlagEnum IL_0064: stloc.1 IL_0065: ldloc.1 IL_0066: box ILLearning.MyFlagEnum IL_006b: ldstr "F" IL_0070: call instance string [mscorlib]System.Enum::ToString(string) IL_0075: call void [mscorlib]System.Console::WriteLine(string) IL_007a: nop IL_007b: ldtoken ILLearning.MyFlagEnum IL_0080: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) IL_0085: ldstr "3" IL_008a: ldc.i4.0 IL_008b: call object [mscorlib]System.Enum::Parse(class [mscorlib]System.Type, string, bool) IL_0090: unbox.any ILLearning.MyFlagEnum IL_0095: stloc.1 IL_0096: ldloc.1 IL_0097: box ILLearning.MyFlagEnum IL_009c: ldstr "F" IL_00a1: call instance string [mscorlib]System.Enum::ToString(string) IL_00a6: call void [mscorlib]System.Console::WriteLine(string) IL_00ab: nop IL_00ac: ldc.i4.s 31 IL_00ae: stloc.1 IL_00af: ldloc.1 IL_00b0: box ILLearning.MyFlagEnum IL_00b5: ldstr "F" IL_00ba: call instance string [mscorlib]System.Enum::ToString(string) IL_00bf: call void [mscorlib]System.Console::WriteLine(string) IL_00c4: nop IL_00c5: ret } // end of method Test::Main .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // 代码大小 8 (0x8) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: nop IL_0007: ret } // end of method Test::.ctor } // end of class ILLearning.Test .class public auto ansi sealed ILLearning.MyFlagEnum extends [mscorlib]System.Enum { .custom instance void [mscorlib]System.FlagsAttribute::.ctor() = ( 01 00 00 00 ) .field public specialname rtspecialname int32 value__ .field public static literal valuetype ILLearning.MyFlagEnum None = int32(0x00000000) .field public static literal valuetype ILLearning.MyFlagEnum Open = int32(0x00000001) .field public static literal valuetype ILLearning.MyFlagEnum Close = int32(0x00000002) .field public static literal valuetype ILLearning.MyFlagEnum All = int32(0x0000001F) } // end of class ILLearning.MyFlagEnum
-
数组
基本概念
- 数组为引用类型,是在托管堆中分配的。
- 数组隐式继承自System.Array
数组转型
对于元素类型为引用类型的数组,CLR允许将数组元素从一种类型转型为另一种。
前提是数组维数相同,而且必须存在从元素源类型到目标类型的隐式转换或者显式转换。
-
CLR不允许将值类型转换成其他任何类型。
// 测试类型转换 FileStream[,] fs = new FileStream[5, 10]; // 隐式转换 object[,] obj = fs;// 支持向上转型,协变性 // 转型失败一:维度不同,不允许转型 // Stream[] fs2 = obj; // 显示转型,成功 Stream[,] fs2 = (Stream[,])obj;// 逆变性 // 转型失败二:编译没有问题,运行有问题,类型不匹配 // string[,] arrStr = (string[,])obj; // 值类型测试 Int32[] arrInt = new int[10]; // 转型失败三:值类型不允许转换成引用类型 // object[] arrObject = arrInt; // 转型失败四:值类型不允许转换成其他值类型 //Double[] arrDouble = arrInt;
数组内部工作原理
- 隐式派生自System.Array
- 所有数组隐式实现IEnumerable、ICollection和IList。
数组的内部工作原理
-
CLR支持两种不同的数组
- 下限为0的一维数组,也称SZ(single-dimensional,zero-based)数组或者向量(Vector)。
- 下限未知的一维或多维数组。
-
演示二维数组的三种方式(安全、交错和不安全)
using System; namespace ILLearning { public class ArrayDemoTest { private static int c_numElements = 10000; public static void Main() { Int32[,] a2Dim = new Int32[c_numElements, c_numElements]; // 声明交错数组(向量构成的向量) int[][] aJagged = new Int32[c_numElements][]; for (int i = 0; i <c_numElements; i++) { aJagged[i] = new Int32[c_numElements]; } Safe2DimArrayAccess(a2Dim); SafeJaggedArrayAccess(aJagged); Unsafe2DimArrayAccess(a2Dim); } /// <summary> /// CLR安全方式访问 /// </summary> /// <param name="a"></param> /// <returns></returns> private static Int32 Safe2DimArrayAccess(Int32[,] a) { Int32 sum = 0; for (int i = 0; i < c_numElements; i++) { for (int j = 0; j < c_numElements; j++) { sum += a[i, j]; } } return sum; } /// <summary> /// 交错访问 /// </summary> /// <param name="a"></param> /// <returns></returns> private static Int32 SafeJaggedArrayAccess(Int32[][] a) { Int32 sum = 0; for (int i = 0; i < c_numElements; i++) { for (int j = 0; j < c_numElements; j++) { sum += a[i][j]; } } return sum; } private static unsafe Int32 Unsafe2DimArrayAccess(Int32[,] a) { Int32 sum = 0; // fixed的作用: /* fixed 语句可防止垃圾回收器重新定位可移动的变量。 fixed 语句仅允许存在于不安全的上下文中。 fixed 还可用于创建固定大小的缓冲区。 */ fixed (Int32* pi = a)// 声明指针pi,指向CLR类型a { for (Int32 i = 0; i < c_numElements; i++) { Int32 baseofDim = i * c_numElements; for (int j = 0; j < c_numElements; j++) { sum += pi[baseofDim+j]; } } } return sum; } } }
-
待分析unsafe代码IL代码
.method private hidebysig static int32 Unsafe2DimArrayAccess(int32[0...,0...] a) cil managed { // 代码大小 118 (0x76) .maxstack 4 .locals init (int32 V_0, int32* V_1, int32[0...,0...] pinned V_2, int32 V_3, int32 V_4, int32 V_5, bool V_6, bool V_7, int32 V_8) IL_0000: nop IL_0001: ldc.i4.0 IL_0002: stloc.0 IL_0003: ldarg.0 IL_0004: dup IL_0005: stloc.2 IL_0006: brfalse.s IL_0010 IL_0008: ldloc.2 IL_0009: callvirt instance int32 [mscorlib]System.Array::get_Length() IL_000e: brtrue.s IL_0015 IL_0010: ldc.i4.0 IL_0011: conv.u IL_0012: stloc.1 IL_0013: br.s IL_001f IL_0015: ldloc.2 IL_0016: ldc.i4.0 IL_0017: ldc.i4.0 IL_0018: call instance int32& int32[0...,0...]::Address(int32, int32) IL_001d: conv.u IL_001e: stloc.1 IL_001f: nop IL_0020: ldc.i4.0 IL_0021: stloc.3 IL_0022: br.s IL_005d IL_0024: nop IL_0025: ldloc.3 IL_0026: ldsfld int32 ILLearning.ArrayDemoTest::c_numElements IL_002b: mul IL_002c: stloc.s V_4 IL_002e: ldc.i4.0 IL_002f: stloc.s V_5 IL_0031: br.s IL_0049 IL_0033: nop IL_0034: ldloc.0 IL_0035: ldloc.1 IL_0036: ldloc.s V_4 IL_0038: ldloc.s V_5 IL_003a: add IL_003b: conv.i IL_003c: ldc.i4.4 IL_003d: mul IL_003e: add IL_003f: ldind.i4 IL_0040: add IL_0041: stloc.0 IL_0042: nop IL_0043: ldloc.s V_5 IL_0045: ldc.i4.1 IL_0046: add IL_0047: stloc.s V_5 IL_0049: ldloc.s V_5 IL_004b: ldsfld int32 ILLearning.ArrayDemoTest::c_numElements IL_0050: clt IL_0052: stloc.s V_6 IL_0054: ldloc.s V_6 IL_0056: brtrue.s IL_0033 IL_0058: nop IL_0059: ldloc.3 IL_005a: ldc.i4.1 IL_005b: add IL_005c: stloc.3 IL_005d: ldloc.3 IL_005e: ldsfld int32 ILLearning.ArrayDemoTest::c_numElements IL_0063: clt IL_0065: stloc.s V_7 IL_0067: ldloc.s V_7 IL_0069: brtrue.s IL_0024 IL_006b: nop IL_006c: ldnull IL_006d: stloc.2 IL_006e: ldloc.0 IL_006f: stloc.s V_8 IL_0071: br.s IL_0073 IL_0073: ldloc.s V_8 IL_0075: ret } // end of method ArrayDemoTest::Unsafe2DimArrayAccess
-
不安全的数组(后续补充)
其他主题
数组的传递和返回
数组作为实参传递给方法时候,实际传递的是数组的引用。
-
Array.Copy方法执行的是浅拷贝,浅拷贝怎么理解?
public class Test { public static void Main(string[] args) { // Array.Copy string[] hhs = new string[3]; hhs[0] = "hi"; hhs[1] = ","; string[] hhs2 = new string[5]; hhs.CopyTo(hhs2,0);// 实际上,这里的效果是深复制,应该是两次New都有在堆中分配了内存空间,不会再单独开辟栈引用,指向其他的内存空间 } }
-
主要IL代码分析
.module test.exe // MVID: {B90E3704-F778-4BDE-8F93-5D1D1296068D} .imagebase 0x00400000 .file alignment 0x00000200 .stackreserve 0x00100000 .subsystem 0x0003 // WINDOWS_CUI .corflags 0x00000001 // ILONLY // Image base: 0x06870000 // =============== CLASS MEMBERS DECLARATION =================== .class public auto ansi beforefieldinit ILLearning.Test extends [mscorlib]System.Object { .method public hidebysig static void Main(string[] args) cil managed { .entrypoint // 代码大小 41 (0x29) .maxstack 3 .locals init (string[] V_0, string[] V_1) IL_0000: nop IL_0001: ldc.i4.3 IL_0002: newarr [mscorlib]System.String // 创建数组hhs IL_0007: stloc.0 IL_0008: ldloc.0 IL_0009: ldc.i4.0 IL_000a: ldstr "hi" IL_000f: stelem.ref // 用计算堆栈上的对象 ref 值(O 类型)替换给定索引处的数组元素。 IL_0010: ldloc.0 IL_0011: ldc.i4.1 IL_0012: ldstr "," IL_0017: stelem.ref IL_0018: ldc.i4.5 IL_0019: newarr [mscorlib]System.String IL_001e: stloc.1 IL_001f: ldloc.0 IL_0020: ldloc.1 IL_0021: ldc.i4.0 IL_0022: callvirt instance void [mscorlib]System.Array::CopyTo(class [mscorlib]System.Array, int32) IL_0027: nop IL_0028: ret } // end of method Test::Main
-
-
数组的实参传递
// 测试数组传递的是引用 public static void Test() { string[] hhs = new string[3]; hhs[0] = "hi"; hhs[1] = ","; foreach (var item in hhs) { Console.WriteLine(item);// hhs[0] = hi } SayHi(hhs); foreach (var item in hhs) { Console.WriteLine(item);// hhs[0] = "HelloWorld" } } public static void SayHi(string[] hehes) { if (hehes != null && hehes.Count() > 1) { hehes[0] = "HelloWorld"; } }
创建下限非零的数组
-
演示案例
// 目前比较少用 public sealed class DynamicArrays { public static void Run() { Int32[] lowerBounds = { 2005, 1 }; Int32[] lengths = { 5, 4 }; Decimal[,] quarterlyRevernue = (Decimal[,])Array.CreateInstance(typeof(Decimal), lengths, lowerBounds); Console.WriteLine("{0,4} {1,9} {2,9} {3,9} {4,9}", "Year","Q1", "Q2", "Q3", "Q4"); Int32 firstYear = quarterlyRevernue.GetLowerBound(0); Int32 lastYear = quarterlyRevernue.GetUpperBound(0); int firstQuarter = quarterlyRevernue.GetLowerBound(1); int lastQuarter = quarterlyRevernue.GetUpperBound(1); for(Int32 year = firstYear;year <= lastYear; year++) { Console.Write(year + " "); for (int quarter = firstQuarter; quarter <= lastQuarter; quarter++) { Console.Write("{0,9:C}", quarterlyRevernue[year, quarter]); } Console.WriteLine(); } } }
可空值类型
基本概念
- System.Nullable<T>仍然为值类型
- ??可空符号优化