首页 > 代码库 > 枚举|标志枚举+|(或)和&(与)运算|类型转换|值类型和引用类型|传参|异常|垃圾回收
枚举|标志枚举+|(或)和&(与)运算|类型转换|值类型和引用类型|传参|异常|垃圾回收
枚举部分
enum 关键字用于声明枚举,即一种由一组称为枚举数列表的命名常量组成的独特类型。
通常情况下,最好是在命名空间内直接定义枚举,以便该命名空间中的所有类都能够同样方便地访问它。 但是,还可以将枚举嵌套在类或结构中。默认情况下,第一个枚举数的值为 0,后面每个枚举数的值依次递增 1。
例1:
//此枚举的默认值是从0开始
enum Days {Sat, Sun, Mon, Tue, Wed, Thu, Fri};
例2:
//此枚举的默认值是从1开始,下标为3的tue值为7,从下标3开始的值依次加1。
enum Days {Sat=1, Sun, Mon, Tue=7, Wed, Thu, Fri};
每种枚举类型都有基础类型,该类型可以是除 char 以外的任何整型。 枚举元素的默认基础类型为 int。 要声明另一整型枚举(如 byte),请在标识符之后紧跟类型,然后再使用冒号,如例3所示。准许使用的枚举类型有 byte、sbyte、short、ushort、int、uint、long 或 ulong。enum E 的默认值为表达式 (E)0 生成的值。
//例3:
//此枚举的类型是byte,值从0到255
enum Days : byte {Sat=1, Sun, Mon, Tue, Wed, Thu, Fri};
枚举数的名称中不能包含空白。
基础类型指定为每个枚举数分配的存储大小。 但是,从 enum 类型到整型的转换需要用显式类型转换来完成。 例如的语句使用强制转换(从 enum 转换为 int)将枚举数 Sun 赋值给一个 int 类型的变量,例4所示。
例4:
int x = (int)Days.Sun;
可以将任意值赋给枚举类型的枚举数列表中的元素,也可以使用计算值,如例5。
例5:
enum MachineState{ PowerOff = 0, Running = 5, Sleeping = 10, Hibernating = Sleeping + 5}
与任何常量一样,对枚举中各个值的所有引用在编译时均将转换为数值文本。
注意:默认输出的是文本值,转换后变为数字。只要给枚举赋予的值在枚举变量容纳枚举定义的值之内,就可以给枚举赋予数字(建议不要这样做)。
标志性枚举
创建位标志枚举的方法是应用 System.FlagsAttribute 特性并适当定义一些值,以便可以对这些值执行 AND、OR、NOT 和 XOR 按位运算。 在位标志枚举中包含一个值为零(表示“未设置任何标志”)的命名常量。如果零值不表示“未设置任何标志”,则请不要为标志指定零值。
[Flags]
enum Days2{ None = 0x0, Sunday = 0x1, Monday = 0x2, Tuesday = 0x4, Wednesday = 0x8, Thursday = 0x10, Friday = 0x20, Saturday = 0x40}
class MyClass{ Days2 meetingDays = Days2.Tuesday | Days2.Thursday;}
为枚举创建新方法
可以使用扩展方法添加特定于某个特定枚举类型的功能。
请参见:http://msdn.microsoft.com/zh-cn/library/bb383974.aspx
枚举小结:
获取当前枚举的基础值类型:Enum.GetLinderlyingType(typeof(MyColor));
枚举转换:
将枚举转换为数值:(int)Mycolor.Red
将枚举转换为字符串:MyColor.Red.ToString()
将字符串转换为枚举:MyColor mc=(MyColor)Enum.Parse(typeof(Type),"value")
标志枚举
枚举加上【Flags】就成为标志枚举,加上这个特性后枚举类型.ToString()返回的就是文字形式,不是数字。
代码如下:
FileAttributes classfileAttributes= FileAttributes.Hidden|FileAttributes.ReadOnly;
Console.WriteLine(classfileAttributes.ToString());//结果是ReadOnly,Hidden;
转到源码查看:
我们修改枚举类,还是图片上的代码,只是去掉所有标记。还用上面的代码,你会发现输出的值是一个数字而不是字符串。
首先我们要知道,普通枚举中的每个值都是互斥的(输出结果只能是一个),而标志枚举中的每个值是可以互相组合的,输出结果可以是多个,多个时值之间用逗号隔开,且计算后的结果是字符而不是数字。(这里说的计算是与或计算,&、|)。当然标志枚举用的string类型(显示的),而计算时用的是赋的值。
该给枚举赋什么样的值呢?
标志枚举规赋值尽量用2的次方进行标记赋值。查看微软的代码会发现:微软用2的次方给枚举赋值。
标志枚举有什么特点呢?
- 单个枚举组合成集合,可以同时输出多个值。用|运算可以枚举值组合在一起,如下。
FileAttributes.ReadOnly| FileAttributes.Hidden进行计算时,把它进行转换:
FileAttributes.ReadOnly :00 00 00 01 →1
FileAttributes.Hidden : 00 00 00 10 →2
|运算——————————————————————————
00 00 00 11 → classfileAttributes的集合
这ClassFileAttribute就是我们取值的集合,然后再经过转换输出输出,输出这个组合的值。
2.判断枚举集合中是否包含某个枚举值。例如:判断FileAttributes.ReadOnly是否在classfileAttributes集合中。
使用代码:If( (classfileAttributes&FileAttributes.ReadOnly)== FileAttributes.ReadOnly)//代码
计算过程:
classfileAttributes : 00 00 00 11
FileAttributes.ReadOnly :00 00 00 10
&运算————————————————————
00 00 00 10 → FileAttributes.ReadOnly
如果FileAttributes.ReadOnly在classfileAttributes集合中,则计算结果为这个值。接着用这个值进比较。
类型转换部分
每个值都有与之关联的类型,此类型定义分配给该值的空间大小、它可以具有的可能值的范围以及它可以提供的成员等特性。许多值可以表示为多种类型。 例如,值 4 可以表示为整数或浮点值。类型转换可以创建一个等同于旧类型值的新类型值,但却不必保留原始对象的恒等值(或精确值)。
.NET Framework 提供了多个功能来支持类型转换。包括:
Implicit 运算符,该运算符定义类型之间可用的扩大转换(隐式转换)。
Explicit 运算符,该运算符定义类型之间可用的收缩转换(显示转换)。
IConvertible 接口,该接口定义到 .NET Framework 每个基数据类型的转换。
Convert 类,该类提供了一组方法来实现 IConvertible 接口中的方法。
TypeConverter 类,该类是一个基类,可以扩展该类以支持指定的类型到任何其他类型的转换。具体:参见此处
上面是.Net Framework中的类型转换,下面直说点C#的。
由于 C# 是在编译时静态类型化的,因此变量在声明后就无法再次声明,或者无法用于存储其他类型的值,除非该类型可以转换为变量的类型。 例如,不存在从整数到任意字符串的转换。 因此,将 i 声明为整数后,就无法将字符串“Hello”赋予它。
类型转换分:
隐式转换:由于该转换是一种安全类型的转换,不会导致数据丢失,因此不需要任何特殊的语法。例如,从较小整数类型到较大整数类型的转换以及从派生类到基类的转换都是这样的转换。
显式转换(强制转换):显式转换需要强制转换运算符。在转换中可能丢失信息时或在出于其他原因转换可能不成功时,必须进行强制转换。典型的例子包括从数值到精度较低或范围较小的类型的转换和从基类实例到派生类的转换。
用户定义的转换:可以定义一些特殊的方法来执行用户定义的转换,从而使不具有基类–派生类关系的自定义类型之间可以显式和隐式转换。
使用帮助程序类的转换:若要在不兼容的类型之间进行转换,例如在整数与 System.DateTime 对象之间转换,或者在十六进制字符串与字节数组之间转换,则可以使用 System.BitConverter 类、System.Convert 类和内置数值类型的 Parse 方法,例如 Int32.Parse。
使用as和is运算符
由于对象是多态的,因此基类类型的变量可以保存派生类型。 若要访问派生类型的方法,需要将值强制转换回该派生类型。 不过,在这些情况下,如果只尝试进行简单的强制转换,会导致引发 InvalidCastException 的风险。 这就是 C# 提供 is 和 as 运算符的原因。 您可以使用这两个运算符来测试强制转换是否会成功,而没有引发异常的风险。 通常,as 运算符更高效一些,因为如果可以成功进行强制转换,它会实际返回强制转换值。 而 is 运算符只返回一个布尔值。 因此,如果只想确定对象的类型,而无需对它进行实际强制转换,则可以使用 is 运算符。
Convert类
简单的说就是将一个基本数据类型转换为另一个基本数据类型。
Convert 类提供了一种与语言无关的方式来执行基类型之间的转换,并且该类可用于面向公共语言运行时的所有语言。 它为扩大转换和收缩转换提供了一组完整的方法,并且会对不支持的转换(例如 DateTime 值到整数值的转换)引发 InvalidCastException。 收缩转换是在已检查的上下文中执行的,如果转换失败,将引发 OverflowException。
除了支持到每个基类型的转换外,Convert 类还可用于将一个自定义类型转换为一个或多个预定义类型。此转换是通过 Convert.ChangeType(Object, Type, IFormatProvider) 方法执行的,而此方法包装了对 value 参数的 IConvertible.ToType 方法的调用。 这意味着 value 参数所表示的对象必须提供 IConvertible 接口的实现。请参见此处。
转换运算符:
C# 允许程序员在类或结构上声明转换,以便类或结构与其他类或结构或者基本类型进行相互转换。 转换的定义方法类似于运算符,并根据它们所转换到的类型命名。 要转换的参数类型或转换结果的类型必须是(不能两者同时都是)包含类型。
转换运算符具有以下特点:
- 声明为 implicit 的转换在需要时自动进行。
- 声明为 explicit 的转换需要调用强制转换。
- 所有转换都必须声明为 static。
如何使用请参见此处。
经验小结:
只有在内存存储上存在交集的类型之间才能进行隐式转换。
显示转换尽量使用as。
将任意类型转换成字符串:ToString(),(object继承下来的,一般都有,当然也可以重写)。
将字符串转换成“数值类型”(int、float、double),可使用Type.Parse(Type type)、int.TryParse(string str,out int n)【经常使用】。
Convert考虑数据意义的转换。Convert转换是重新加工、改造的过程,也就是从新组织数据结构。Convert可以把object类型转换为其他类型。Convert可以把任意类型转换为任意类型。
自娱自乐
Type.TryParse(Type type)有什么优点呢?
答:Parse()转换失败会抛异常,TryParse()转换失败不报异常。例如:int result;bool p=int.TryPase(result,out result) 转换失败的话是0。
为什么说as比is转换比较高效呢?
例如:if(p is Student){Student stu=(Student)p;},CLR会进行两次类型检查if(检查一次){//再检查一次},而as是直接转换,如果转换失败返回null而不会报异常。Is也可以用GetType()获得类型,然后转换。GetType()不允许重写。
值类型和引用类型部分
值类型
如果数据类型在它自己的内存分配中存储数据,则该数据类型就是“值类型”。 值类型包括:
- 所有数字数据类型
- Boolean 、Char 和 Date
- 所有结构,即使其成员是引用类型
- 枚举,因为其基础类型总是 SByte、Short、Integer、Long、Byte、UShort、UInteger 或 ULong
每个结构是值类型,即使它包含引用类型成员。
可以通过使用保留关键字(例如 Decimal)声明值类型。 也可以使用 New 关键字初始化值类型。 这对于值类型有一个带参数的构造函数的情况尤为有用。
引用类型
“引用类型”包含指向存储数据的其他内存位置的指针。 引用类型包括:
- String
- 所有数组,即使其元素是值类型
- 类类型,如 Form
- 委托
类是一种“引用类型”。 因此,诸如 Object 和 String 之类的引用类型都受 .NET Framework 类支持。 注意,每个数组都是一种引用类型,即使其成员是值类型。
由于每种引用类型表示基础 .NET framework 类,在初始化时,则必须使用 New 运算符关键字,如:Dim totals() As Single = New Single(8) {};
非类型的元素
以下编程元素未限定为类型,因为您无法将它们中的任何一个指定为声明元素的数据类型:
- 命名空间
- 模块
- 事件
- 属性和过程
- 变量、常数和字段
使用对象数据类型
可以将引用类型或值类型指派给 Object 数据类型的变量。 Object 变量总是存储指向数据的指针,从不存储数据本身。 然而,如果为 Object 变量指派值类型,该变量的行为将如同存储自己的数据一样。可参考该地址。
性能问题
数据传入方法作为值类型参数,即在堆栈上创建了每个参数的副本。很明显,如果相关参数是大型数据类型(例如,包含很多元素的用户定义结构),或多次执行方法,都可能影响性能。在这些情况下,使用 ref 关键字向类型传递一个引用可能更为可取。out 关键字类似于 ref 关键字,但它会告知编译器该方法必须为参数赋值,否则将产生编译错误。
例子:
值类型的内容存储在堆栈上分配的内存中。例如 int x=42,值 42 存储在称为“栈”的内存区域中。
定义变量的方法在结束执行或使变量 x 超出使用范围时,其值则从栈中丢弃。使用栈效率较高,但值类型的生命周期有限,不适合在不同类之间共享数据。
引用类型(例如,类或数组的实例)在另一个称为“堆”的内存区域中分配。
例如int[] numbers = new int[10],构成数组的 10 个整数所需的空间是在堆上分配的。在方法完成时,并不将此内存归还给堆;仅当 C# 的垃圾回收系统确定不再需要该内存时,才进行回收。声明引用类型需要更多系统开销,但它们的优点是可以从其他类进行访问。
经验小结:
一个变量是使用基本的内置数据类型之一或用户定义的结构进行声明的,则该变量为值类型。但 string 数据类型除外,它是引用类型。
所有引用类型都继承Object。所有的值类型都继承自System.ValueType[System.ValueType]又继承自Object。
值类型和引用类型都用到了栈,值类型的数据存储在栈上。引用类型的引用地址是存在栈上的,数据是存在堆中。
引用类型的变量赋值只复制对象的引用。值类型变量赋值会拷贝一个副本。
Out和Ref:
ref 仅仅是一个地址,引用传递,把值传递强制改为引用传递。
1.在方法中必须为out参数赋值,out 让函数可以输出多个值。
2.out参数的变量在传递之前不需要赋值,即使赋值了也不能在方法中使用。
ref 参数在传递之前必须赋值。在方法中可以不为ref参数赋值,也可以直接使用。
区别是:ref应用场景内部对外部的值进行改变,out则是内部为外部变量赋值,out一般在函数有多个返回值的场所。当一个方法同时要返回多个值的时候,可以考虑使用out参数。
ref主要是把一个值,带进去再带出来。out主要是带出来。
异常部分
在运行时发生的错误才叫异常。语法错误,编译器可以提示,逻辑错误,编译没错,结果不正确,会使程序崩溃。程序崩溃了也要执行finally。
catch{}//可以捕获所有的异常,catcha(Exception ex){} 获得异常的详细详细对异常进行分类,
内部异常直接抛,最外层调用者进行处理。注:throw;只能在 catch中写。try中有return,finally也照样执行,但finally中不能写return语句。
发生异常后,try块中,异常代码后的代码不会执行。finally块中的代码,无论是否发生异常都会执行。
异常对象:Exception ex。此类封装了异常发生时的一些信息。
Exception 类主要属性:Message、StrackTrace、InnerException
扔出自己的异常用throw,抓住:catch。
注意:通过逻辑判断(if-else)减少异常发生的可能性。在多级方法嵌套调用的时候,如果发生了异常,则会终止所有相关方法的调用,并且释放相关的资源。
传参部分
在封装类时经常封装方法或构造函数等,封装时经常需要传递参数。而传参是分值传递和引用传递。传参的时候,值传递中传递的是栈中保存的数据,而引用传递则是传递本身的地址(栈和堆各有一个地址)。
值传递:传递的是栈中的内容,例如:值类型或者引用类型作为参数传递。
引用类型:需借助ref和out进行传递。
引用类型的变量(字符了,例如 String str=null中,str就是个变量)是在栈上存储,堆中开辟地址存放数据。传参时,引用对象不仅传参的是地址引用,同时也是一个新的对象。例如:在类中的方法传递的是类时,此时传递进方法体中的参数是一个新的对象。例如:void method(class name){}。name的对象是一个新的对象,只是这个对象指向传递进来的对象的地址,也不全是个占位符。当在这个方法体中重新赋予新的对象时(name=new class(); ),也就重新指向新的地址了(栈上的地址不变,指向堆上的地址发生变化),这时修改这个值时,原来的对象(方法中传进来的)不会改变。
小结:我感觉有点绕,不过我会绕出来的,写点方便看懂的。简单记住就是:值传递和引用传递都可传递引用类型和值类型的值,只是值传递是栈中对象的拷贝(拷贝,赝品,但是一个新的),引用传递是栈中地址,就是同一对象了。
不过传递参数尽量用接口>抽象类>父类。
补充一点:params可以为可变参数直接传递一个对应类型的数组。
垃圾回收
.NET Framework 的垃圾回收器管理应用程序的内存分配和释放。 每当您创建新对象时,公共语言运行时都会从托管堆为该对象分配内存。只要托管堆中有地址空间可用,运行时就会继续为新对象分配空间。 但是,内存不是无限大的。 最终,垃圾回收器必须执行回收以释放一些内存。 垃圾回收器优化引擎根据正在进行的分配情况确定执行回收的最佳时间。 当垃圾回收器执行回收时,它检查托管堆中不再被应用程序使用的对象并执行必要的操作来回收它们占用的内存。
垃圾回收的基础、垃圾回收的性能、被动回收、滞后时间模式、针对共享Web承载优化、垃圾回收通知、应用程序域资源监控、弱引用。请参考此处。
小结:
时间问题,垃圾回收就偷个懒了,O(∩_∩)O~。
垃圾回收的目的:提高内存利用率。
垃圾回收器,只回收托管堆中的内存资源。不回收其他资源(数据库连接、文件句柄、网络端口等)
什么样的对象才会被回收?
没有变量引用的对象。没有变量引用的对象,表示可以被回收(null)。
什么时间回收?
不确定,当程序需要新内存的时候开始执行回收。
GC.Collect();//手动调用垃圾回收器。不建议使用,垃圾回收时会暂停一下(非常短暂)让程序区自动调用GC。
垃圾回收器中"代"的概念:
共3代:第0代、第1代、第2代。各代的回收频率:第0代最高,其次第1代,再次第2代。也就是说越老的对象生存几率越大。
除了内存资源外的其他资源怎么办? Dispose()
弱引用:WeakReference,对于创建比较耗时的对象可以使用弱引用。
弱引用例子:
person p=new person();
p.Age=13;
WeakReference wk=WeakReference(p);//弱引用
p=null;//当执行完这句话的时候,垃圾回收可以去回收p对象。通常弱引用还可以访问到p。
object pnew=wk.Target;
//垃圾回收不知道什么时候进行,所以存在漏洞。一般不用isLive。
if(pnew != null)
{Console.WriteLine(((Person)pnew).Age);}
else{Console.WriteLine("对象已经被回收!")};
//弱引用在大括号内有效。
弱引用小结:
弱引用一般存在那些创建是比较耗时的对象。例如打开数据库取出比较多的数据时,只要在垃圾回收之前,弱引用对该对象还是有效地。弱引用就相当于存一个源对象的索引,只要源对象没被垃圾回收进行回收,弱引用就可以引用。