zoukankan      html  css  js  c++  java
  • C# 基础

    主要包括:基类、常量、变量、数据类型、String类、new的用法、类型转换、进制转换、装箱与拆箱、预处理器指令、final

    基类

    1、C#中所有类型的基类是什么?

    答:在.NET CTS(Common TypeSystem)中,每一个类型都直接或间接继承自Object类,所有这些类型其实都包含于命名空间System中,所以C#中所有类型的基类是System.Object。CTS中所定义的每种类型,如果不是引用类型,那么就是值类型。引用类型直接继承自Object,所有值类型直接继承自ValueType,ValueType又继承自Object

    .NET中引用类型包括:Class, Interface, Array,String, Delegate等,除此之外的那些类型都是值类型。 

    2、C#的基类都有哪些方法

    在C#中,Object类型是所有类型的根。Object类型中一共有8个方法,重载的方法没有算进来:

    构造函数Object()、ToString()函数、GetHashCode函数、

    Equals函数、ReferenceEquals函数、

    Finalize函数、GetType()函数、MemberWiseClone()函数

    下面一一来看看这些方法。

    1、构造函数 public Object()

      直接使用new Object()可以用来创建对象;如果非Object类型,则在该类型的构造函数被调用时,该函数自动被调用。

    2、ToString()方法:是获取对象字符串表示的一种快捷方式。当只需要快速获取对象的内容,以进行调试时,就可以使用这个方法。

    3、GetHashCode()方法:用作特定类型的哈希函数,快速生成一个与对象的值相对应的数字(哈希代码)。此方法适用于哈希算法和诸如哈希表之类的数据结构。

    4、Equals()和ReferenceEquals()方法:

    Equals用来检测两个对象是否相等,即两个对象的内容是否相等,区分大小写

    ReferenceEquals判断两个引用是否引用类的同一个实例。从描述也可以看出来,如果参数是值类型,则会装箱,比较的是装箱后的对象实例。

    ==用于比较引用和比较基本数据类型时具有不同的功能:

      比较基本数据类型,如果两个值相同,则结果为true

      而在比较引用时,如果引用指向内存中的同一对象,结果为true

    5、Finalize()方法:允许对象在垃圾回收回收该对象之前 尝试释放资源并执行其它清理操作。

    6、GetType()方法:获取当前实例的确切运行时类型

    7、MemberWiseClone()方法:创建一个浅表副本,方法是创建一个新对象,然后将当前对象的非静态字段复制到该新对象。

    如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其复本引用同一对象。

     

    常量(const readonly)

    C#有两种常量类型:编译时常量(用const声明)与运行时常量(用readonly声明)
    编译时常量可以在方法内声明,而运行时常量不可以。
    编译时常量只能是原子类型(内置整型、浮点型、枚举、string等),而运行时常量可以是任何类型。
    最重要的是Readonly类型变量在运行时被解释,IL会生成对应对象的常引用 ,而const变量会被IL生成对应的值,而这样会对后期的维护造成不便。

    编译时常量const在编译的时候会被直接写成对应的值,而不会再从原来的类中读取。这样会产生问题。

    如果A类定义了常量, B类使用了常量, 并且都进行了编译, 当A类的源码被改动了, 常量的值发生了变化, 我们对A类进行重新编译, 但是没有对B进行重新编译, 那么B类中用到的是原来A类中的常量值, 即旧值, 这样就导致了风险的产生 。

    但是理论上是这样的,实际上操作中IDE会自动帮我们重新编译B类, 所以我试着操作了几次,并没有发生这样的风险。但是这个风险的确是存在的。

    如下代码:

    public class UsefulValues
    {
      public static readonly int StartValue = 5;
      public const int EndValue = 10;
    }

    而在另一程序集,你引用了这些值:

    for(int i = UsefulValues.StartValue;i<UsefulValues.EndValue; i++)
       Console.WriteLine("value is {0}", i);

    然后经过一段事件你对原程序集(UsefulValues)进行了修改,修改后:

    public class UsefulValues
    {
      public static readonly int StartValue = 105;
      public const int EndValue = 120;
    }

    此时你希望输出是
    value is 105
    value is 106
    ...
    value is 119
    实际将什么都不输出,因为此时的for循环已经是如下代码:

    for(int i = UsefulValues.StartValue;i<10; i++)
       Console.WriteLine("value is {0}", i);

    const变量会比readonly变量要稍微快一点,因为IL是直接为其生成常量字符串或数字,而readonly的灵活性要更好,因此我们应当偏爱readonly

    装箱和拆箱

    其实就是数据类型转换。装箱是把值类型转换为引用类型的过程,是隐式的,相反的过程就是拆箱,是显式的。

    装箱分3部:
    1)分配内存空间。包括要装箱的值类型的空间、方法表、SynBlockIndex,其中后两者用来管理引用对象。
    2)值复制。把堆栈中要装箱的值复制到堆上。
    3)返回引用对象的引用。
    拆箱也分3部:
    1) 检查类型,确保引用类型是装箱的结果。
    2)指针返回,返回要拆箱的引用类型中的值的地址。
    3)字段拷贝,把引用类型中的字段拷贝到堆栈中。

    new关键字用法

    1、new 运算符:用于创建对象和调用构造函数。

    2、new 修饰符:可以显式隐藏从基类继承的成员。

    当基类和子类中都具有相同签名的方法声明,而基类方法没有使用virtual关键字时,子类方法便会隐藏基类方法。子类隐藏基类的方式时,编译器会进行提示:若是有意隐藏基类方法,(子类)请使用关键字new。

    class TestBaseClass
    {
        public void VirtualMethod()
        {
            Console.WriteLine("This Is Base Class's Virtual Method");
        }
    }
     
    class TestDerivedClass:TestBaseClass
    {
        public new void VirtualMethod()//使用new关键字告诉编译器隐藏基类方法
        {
            Console.WriteLine("This Is Derived Class's Virtual Method");
        }
    View Code

    3、new约束:用于在泛型声明中约束可能用作类型参数的参数的类型。

    public class Tester<T>  where T:new()  
    {
    }

    where T : class   T必须是一个类(class)类型
    where T : new()   T必须要有一个无参构造函数

    进制转换

    int 10进制转16进制

    int.ToString("x");  //字母小写 

    int.ToString("X");  //字母大写

    或者

    // 方式1,无法进行自动补0
    string data1 = Convert.ToString(17194, 16); // 432A
    string data2 = Convert.ToString(0, 16); // 0
     
    // 方式2,自动补0
    string data3 = 17194.ToString("X4"); // 432A
    string data4 = 0.ToString("X2"); // 00
    string data5 = 0.ToString("X4"); // 0000

    X代表16进制;X后面的数字每次的数据位数,当位数不足时自动补0

     xx进制转10进制(int)

    1、二进制转10进制:System.Convert.ToInt32(s, 2);// s为string类型 以“1010”为例,输出为10

    2、16进制转10进制:System.Convert.ToInt32("0x41", 16);//以"0x41"为例,输出为65

    xx进制转2进制(string输出)

    1、10进制转2进制:System.Convert.ToString(d, 2);// d为int类型 以4为例,输出为100

    2、16进制转2进制:System.Convert.ToString(d, 2);// d为int类型 以0X14为例,输出为10100

    类型转换

    int类型转换方法

    Convert.ToInt32、int.Parse(Int32.Parse)、int.TryParse、(int) 四者都可以解释为将类型转换为 int,那它们的区别是什么呢?

    • Convert.ToInt32 与 int.Parse 较为类似,实际上 Convert.ToInt32 内部调用了 int.Parse:
    • Convert.ToInt32 参数为 null 时,返回 0; 参数为 "" 或者"  "时,抛出异常。Convert.ToInt32 可以转换的类型较多;
    • int.Parse 参数为 null 或者 "" 时,抛出异常。int.Parse 只能转换数字类型的字符串。
    • int.TryParse 与 int.Parse 又较为类似,但它不会产生异常,转换成功返回 true,转换失败返回 false。最后一个参数为输出值,如果转换失败,输出值为 0。
    • (int) 属 cast 转换,只能将其它数字类型转换成 int 类型,它不能转换字符串,否则报异常。

    as、is、cast 

    请使用as来进行两个对象之间的类型转换(转换失败时返回null,不会引发异常)因为它更安全、更有效。

    • as 运算符只执行引用转换和装箱转换,不能用于值类型,as 运算符无法执行其他转换。

    as与is操作符不能执行任何的用户自定义类型转换,它从不自己构造新对象。

    • Cast则可实现类型转换,它会转换一个对象为请求的类型,如果转换一个高精度类型到低精度类型,则可能会丢失信息。

    值类型只能用Cast,而此时会产生装箱/拆箱操作,而此时一般建议会使用is操作符来先判断。如:

    object o = Factory.GetValue();
    int i = 0;
    if(o is int)
      i=(int)o;

    is操作符应该用在不能使用as操作符进行转换的时候。is检查对象是否与给定类型是否相同,返回true/false
    好的面向对象设计应当尽量避免进行类型转换,但是有时你不得不进行类型转换的时候,使用as和is操作符可以更清晰地表达你的目的。

    隐式、显示转换

    示例:

    short s1 = 1; s1 = s1 + 1;  //报错:int转为short,不能隐式转换, 缺少强制转换

    而  short s1=1;s1+=1;      //正确,是因为编译的时候,会自动转换。即 s1=(short)(s1+1);

    用反编译工具Reflector看一下就知道了

     

    预处理器指令 

    #if / #endif 及其改进

    #if/#endif太容易滥用,创建的代码也很难理解和调试。C#增加了条件特性来指示一个方法是否应该被调用。它比#if/#endif更清晰。

    1、#if/#endif方式:

     private void CheckStateBad()
            {
                // The Old way:
    #if DEBUG
                Trace.WriteLine("Entering CheckState for Person");
                // Grab the name of the calling routine:
                string methodName = new StackTrace().GetFrame(1).GetMethod().Name;
                Debug.Assert(lastName != null, methodName, "Last Name cannot be null");
                Debug.Assert(lastName.Length > 0, methodName, "Last Name cannot be blank");
                Debug.Assert(firstName != null, methodName, "First Name cannot be null");
                Debug.Assert(firstName.Length > 0, methodName, "First Name cannot be blank");
                Trace.WriteLine("Exiting CheckState for Person");
    #endif
            }
    View Code

    如果在Release模式编译生成,此方法将会是一个空方法。如果频繁地调用该方法也会产生一部分开销。而有时因为#if/#endif的位置不当,Release编译时会产生一些错误。

    2、条件特性方式:
    [Conditional("DEBUG")]
            private void CheckStateBad()
            {
    ...
    }
    该特性告诉编译器该方法只有在检测到有DEBUG环境变量时有效。
    条件特性相比#if/#endif会生成更有效的IL代码

    final, finally, finalize的区别

    final,如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。

    finally,在异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入finally 块(如果有的话)。

    finalize,方法名。Finallize表示是object类一个方法,在垃圾回收机制中执行的时候会被调用被回收对象的方法。

    C#中的对象释放

    c# 自身对于所有托管对象(managed object)都是可以不用程序员去干预的(注:可以不用干预,当然资源耗费很多且必要时也可以去做一些干预来提升程序性能,比如通过代码部分控制垃圾回收),但对于非托管资源(文件、流、数据库连接、GDI+对象、COM对象等等)都需要程序来控制资源的释放。

    释放资源主要有两种方式:

    • 其一是对象实现IDisposable接口,由程序员调用IDisposable.Dispose()方法来释放资源;
    • 其二是通过重写继承自Object对象的虚方法Finalize()来释放资源。

    (一)IDisposable

    public interface IDisposable
    {
       void Dispose();
    }

     说明:垃圾回收器GC不支持IDisposable接口,不能通过自动的垃圾回收机制或手工调用GC.Collect()方法来调用Disposable方法。

     重要点:只有通过程序显式或非显式调用Dispose方法,才会通过该方法释放资源。所谓显示调用,就是直接在代码中写Dispose()来带调用。关于隐式调用,常见的有两种:

    (1)其一就是使用using关键字

      using(StreamWriter sw= new StreamWriter(NewFilePath,true))
         {
               //处理完代码后,会调用Dispose方法。即使发生了异常,也会调用Dispose方法。本质上中间代码是转换为try{}finally{}的,而Dispose方法是在finally{}中调用的,所以不担心因为异常而没有调用到Dispose方法 
        }

    (2)关于Dispose方法的隐式调用,另一种常见的是对Dispose的间接调用而已,常见的是一些类库中的别名调用。比如

     FileSream fs=new FileStream("abc.txt“,FileMode.OpenOrCreate);
          //dosomething
          fs.close();    //close()释放资源,实质上就是隐式调用了Dispose()释放了资源。

    注:如果不调用close()而调用Dispose(),其效果是一样的。

    (二)Finalize()方法与析构函数~MyClass()
     要点:Finalize是由Object基类定义的虚方法;垃圾回收的时候GC会调用Finalize()方法;在自己的程序中,通过c#是不能直接重载Finalize()方法的,会出现编译错误,what?why?how?那怎么办呢?答案是:用析构函数~MyClass()来代替Finalize()方法,在析构函数中写释放资源的代码。

    class MyClass
    {
        protected override void Finalize(){}    //是不能编译的,编译不通过
    //为什么会这样呢。Finalize方法本来就是Object类定义的受保护的虚方法(protected virtual),这里却不能重写???
    //不知道微软是怎么处理的,但就是不能继承,咱也没有办法的,那就不要这么做了。微软要我们直接写析构函数~MyClass(){}
    
    }
    
    class MyClass
    {
     protected override void Finalize(){}    //是不能编译的,编译不通过
       ~MyClass()
          {
               MyReleaseSourceCode;         //在这里写释放资源的代码
         }
    }
    View Code

    经过仔细查证,发现:析构函数经过编译后,在中间代码中就会转换为Finalize()方法,所以c#的析构函数本质上就是Finalize()方法。但是,通过析构函数转换为Finalize()方法的过程中,编译器会添加一些额外的代码。通过ildasm.exe查看c#析构函数编译过的代码,可以发现编译器加入了一些错误检测代码。分析中间码可以发现,程序员自己写的代码都会放在中间代码的try{}块中,另外在中间代码的finally块中放置了一些代码,保证基类的Finalize方法总是被执行。

     

  • 相关阅读:
    java 前端-BOM
    java web -tomcat
    java基础-递归
    java基础-正则表达式
    Hibernate从入门到精通(六)一对一双向关联映射
    Hibernate从入门到精通(五)一对一单向关联映射
    Hibernate从入门到精通(四)基本映射
    Hibernate从入门到精通(三)Hibernate配置文件
    Hibernate从入门到精通(二)Hibernate实例演示
    洛谷 P2701 [USACO5.3]巨大的牛棚Big Barn
  • 原文地址:https://www.cnblogs.com/peterYong/p/11366248.html
Copyright © 2011-2022 走看看