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方法总是被执行。

     

  • 相关阅读:
    LeetCode 88. Merge Sorted Array
    LeetCode 75. Sort Colors
    LeetCode 581. Shortest Unsorted Continuous Subarray
    LeetCode 20. Valid Parentheses
    LeetCode 53. Maximum Subarray
    LeetCode 461. Hamming Distance
    LeetCode 448. Find All Numbers Disappeared in an Array
    LeetCode 976. Largest Perimeter Triangle
    LeetCode 1295. Find Numbers with Even Number of Digits
    如何自学并且系统学习计算机网络?(知乎问答)
  • 原文地址:https://www.cnblogs.com/peterYong/p/11366248.html
Copyright © 2011-2022 走看看