C#笔试面试题

C#中堆和栈的区别

  • 堆与栈的概念及不同点:
    • 在内存中栈主要负责处理线程中的命令,并且是以栈Stack的形式读取与执行的;
    • 堆主要是存储方法体以及数据,类似于床上散落的衣服,可供随机读取。
  • 堆和栈上的垃圾回收:
    • 栈有自我维护特性,执行完语句马上释放不会造成资源泄漏。
    • 堆则需GC回收,并且符合GC回收的规则,很多堆上的内容在程序退出前都没有被回收,很可能是无意中某处还保留着内容的引用导致,这将严重影响性能。
  • 内存的分配:

    • 堆栈(stack)是一种后进先出的数据结构,在内存中,变量会被分配在堆栈上来进行操作。
    • 堆(heap)是用于为类型实例(对象)分配空间的内存区域,在堆上创建一个对象,会将对象的地址传给堆栈上的变量(反过来叫变量指向此对象,或者变量引用此对象)。
  • 相关阅读

C#中值类型和引用类型的区别

  • C# 中的类型一共分为两类,一类是值类型(Value Type),一类是引用类型(Reference Type )。
  • 值类型与引用类型不同点:引用类型永远存在于托管堆上,值类型在哪取决于声明的位置。
  • 值类型包括 结构和枚举,引用类型包括类、接口、委托 等。还有一种特殊的值类型,称为简单类型(Simple Type),比如 byte,int等,这些简单类型实际上是FCL类库类型的别名,比如声明一个int类型,实际上是声明一个System.Int32结构类型。因此,在Int32类型中定义的操作,都可以应用在int类型上,比如 “123.Equals(2)”。
  • 所有的 值类型 都隐式地继承自 System.ValueType类型(注意System.ValueType本身是一个类类型)
  • System.ValueType和所有的引用类型都继承自 System.Object基类。
  • 你不能显示地让结构继承一个类,因为C#不支持多重继承,而结构已经隐式继承自ValueType
  • 值类型继承自System.ValueType ; 引用类型继承自System.Object
  • 值类型与引用类型在改变内容时处理的方式不同:值类型执行内容拷贝,引用类型始终更改的是所引用的内容,这将导致两者行为上的不一致。
  • 值类型当参数时,复制拷贝为一个栈上的新对象,使用后回收。
  • 值类型当参数时,会发生拷贝现象,所以对一些“很大”的结构体类型会产生很严重的效率问题,可尝试用ref 关键字将结构体包装成引用类型进行传递,节省空间及时间。
  • 引用类型传递的是引用地址,即多个事物指向同一个内存块,如果更改内存中的值将同时反馈到所有其引用的对象上。
  • ref关键字传递的是引用类型的指针,而非引用类型地址。(当声明参数带有ref 关键字时,引用类型传递的是引用类型的指针,相反如果没有ref关键字,参数传递的是新的指向引用内容的指针(引用))
  • 相关阅读: C# 类型基础

C#中有指针吗?

  • C#为了类型安全,默认并不支持指针。但是也并不是说C#不支持指针,我们可以使用unsafe关键词,开启不安全代码(unsafe code)开发模式。在不安全模式下,我们可以直接操作内存,这样就可以使用指针了。在不安全模式下,CLR并不检测unsafe代码的安全,而是直接执行代码。unsafe代码的安全需要开发人员自行检测。

  • 相关阅读 :C# 不安全代码

C#中结构体和类的区别是什么

C#中装箱和拆箱的区别

  • 装箱是将值类型转换为 object 类型或由此值类型实现的任何接口类型的过程。

  • 值类型进行装箱时,会将该值包装到System.Object 内部,再将后者存储在托管堆上。

  • 取消装箱将从对象中提取值类型

  • 装箱是隐式的(也可以进行显式装箱);取消装箱是显式的。

    //隐式装箱
    int i = 123;
    object o = i;  
    
    //显式装箱
    int i = 123;
    object o = (object)i;  // explicit boxing
    
    //显示取消装箱
    int i = 123;      // a value type
    object o = i;     // boxing
    int j = (int)o;   // unboxing

  • 相对于简单的赋值而言,装箱和取消装箱过程需要进行大量的计算。 对值类型进行装箱时,必须分配并构造一个新对象。 取消装箱所需的强制转换也需要进行大量的计算,只是程度较轻。

  • 如何在 C# 中使用装箱。

    扫描二维码关注公众号,回复: 954752 查看本文章
  // String.Concat example.
  // String.Concat has many versions. Rest the mouse pointer on 
  // Concat in the following statement to verify that the version
  // that is used here takes three object arguments. Both 42 and
  // true must be boxed.
  Console.WriteLine(String.Concat("Answer", 42, true));


  // List example.
  // Create a list of objects to hold a heterogeneous collection 
  // of elements.
  List<object> mixedList = new List<object>();

  // Add a string element to the list. 
  mixedList.Add("First Group:");

  // Add some integers to the list. 
  for (int j = 1; j < 5; j++)
  {
      // Rest the mouse pointer over j to verify that you are adding
      // an int to a list of objects. Each element j is boxed when 
      // you add j to mixedList.
      mixedList.Add(j);
  }

  // Add another string and more integers.
  mixedList.Add("Second Group:");
  for (int j = 5; j < 10; j++)
  {
      mixedList.Add(j);
  }

  // Display the elements in the list. Declare the loop variable by 
  // using var, so that the compiler assigns its type.
  foreach (var item in mixedList)
  {
      // Rest the mouse pointer over item to verify that the elements
      // of mixedList are objects.
      Console.WriteLine(item);
  }

  // The following loop sums the squares of the first group of boxed
  // integers in mixedList. The list elements are objects, and cannot
  // be multiplied or added to the sum until they are unboxed. The
  // unboxing must be done explicitly.
  var sum = 0;
  for (var j = 1; j < 5; j++)
  {
      // The following statement causes a compiler error: Operator 
      // '*' cannot be applied to operands of type 'object' and
      // 'object'. 
      //sum += mixedList[j] * mixedList[j]);


  // After the list elements are unboxed, the computation does 
  // not cause a compiler error.
  sum += (int)mixedList[j] * (int)mixedList[j];



  }

  // The sum displayed is 30, the sum of 1 + 4 + 9 + 16.
  Console.WriteLine("Sum: " + sum);

  // Output:
  // Answer42True
  // First Group:
  // 1
  // 2
  // 3
  // 4
  // Second Group:
  // 5
  // 6
  // 7
  // 8
  // 9
  // Sum: 30

装箱拆箱示意图:
装箱

图片来源

一个类Class A , 要当作Dictionary<TKey, TValue>中的TKey, 应该重写什么方法?

  • 参考答案:

    GetHashCodeEquals

  • 扩展:

0. hashCode方法及equals方法的规范

1. MSDN: Object.GetHashCode( )

哈希代码旨在高效插入和基于哈希表的集合中查找。 哈希代码不是永久的值。 出于此原因︰
- 不要序列化哈希代码值或将它们存储在数据库中。
- 不使用的哈希代码作为键从键控集合中检索对象。
- 不要跨应用程序域或进程发送的哈希代码。 在某些情况下,可能会基于每个进程或每个应用程序域计算哈希代码。(说明哈希代码可能不同)
- 不要判断哈希代码是否相等,来两个对象是否相等。 (不相等的对象可以具有相同的哈希代码。) 若要测试相等性,调用ReferenceEqualsEquals方法。

2. MSDN: Object.Equals (Object)

  • 如果当前实例是引用类型,Equals(Object)方法测试引用相等性,并调用Equals(Object)方法等效于调用ReferenceEquals方法。 引用相等性意味着进行比较的对象变量引用同一个对象。
  • 如果当前实例是值类型,Equals(Object)方法测试值是否相等。 值相等性意味着︰
    • 1.两个对象均为相同的类型。 如下面的示例所示,Byte的值为 12 的对象不等于Int32具有其值为 12,因为两个对象具有不同的运行时类型的对象。
  using System;

  public class Example
  {
     public static void Main()
     {
        byte value1 = 12;
        int value2 = 12;

        object object1 = value1;
        object object2 = value2;

        Console.WriteLine("{0} ({1}) = {2} ({3}): {4}",
                          object1, object1.GetType().Name,
                          object2, object2.GetType().Name,
                          object1.Equals(object2));
     }
  }
  // The example displays the following output:
  //        12 (Byte) = 12 (Int32): False
  • 2.两个对象的公共和私有字段的值相等。下面的示例测试的值相等。 它定义Person结构,这是值类型,并调用构造函数来实例化两个新Person对象,person1和person2,其中具有相同的值。 如示例输出所示,虽然两个对象变量引用不同的对象,但person1和person2是否相等的因为它们具有相同的值为私有personName字段。
  using System;

  // Define a value type that does not override Equals.
  public struct Person
  {
     private string personName;

     public Person(string name)
     {
        this.personName = name;
     }

     public override string ToString()
     {
        return this.personName;
     }
  }

  public struct Example
  {
     public static void Main()
     {
        Person person1 = new Person("John");
        Person person2 = new Person("John");

        Console.WriteLine("Calling Equals:"); 
        Console.WriteLine(person1.Equals(person2)); 

        Console.WriteLine("\nCasting to an Object and calling Equals:");
        Console.WriteLine(((object) person1).Equals((object) person2));  
     }
  }
  // The example displays the following output:
  //       Calling Equals:
  //       True
  //       
  //       Casting to an Object and calling Equals:
  //       True

Interface和抽象类的区别

C#中的抽象类(abstract class)和接口(interface)的比较:

  • 抽象类是一个不完整的类,需要进一步细化;接口只是一个行为的规范或规定。
  • 抽象基类可以定义字段、属性和方法实现;接口只能定义属性、索引器、事件和方法声明,不能包含字段。
  • 抽象类更多的是定义在一系列关系紧密的类之间;接口大多定义在关系稀松但都实现某一功能的操作。
  • 接口不具有继承的任何特点。
  • 接口可以被多重实现,抽象类只能被单一继承。
  • 接口支持回调,抽象类不可以。
  • 抽象类不能密封。
  • 抽象类实现的具体方法默认为虚的;但实现接口的类中的接口方法默认不是虚的,当然也可手动声明为虚的。
  • 接口和非抽象类类似,抽象类必须为在该类的基类列表中列出的接口的所有成员提供他自己的实现。但是允许抽象类将接口的方法隐射到抽象方法上。
  • 如果抽象类实现接口,则可以把接口中的方法映射到抽象类中作为抽象方法而不必实现,而是在抽象类的子类中实现接口中的方法。

ref和out的区别

  • ref 关键字 是作用是把一个变量的引用传入函数,和 C/C++ 中的指针几乎一样,就是传入了这个变量的栈指针。
  • out 关键字 的作用是当你需要返回多个变量的时候,可以把一个变量加上 out 关键字,并在函数内对它赋值,以实现返回多个变量。
  • 参考阅读: 把 ref 和 out 关键字说透

提高GC效率的方法:

  1. 清理干净。不要保持资源一直开启!确定的关闭所有已打开的连接,尽可能的清理所有非托管对象。当使用非托管资源时一个原则是:尽可能晚的初始化对象并且尽快释放掉资源。

  2. 不要过度的使用引用。合理的利用引用对象。记住,如果我们的对象还存活,我们应该将对象设置为null。我在设置空值时的一个技巧是使用NullObject模式来避免空引用带来的异常。当GC开始回收时越少的引用对象存在,越有利于性能。

  3. 简单的用终结器(终结器隐式调用对象基类上的 Finalize)。对GC来说终结器十分消耗资源,我们只有在十分确定的方式下使用终结器。如果我们能用IDisposeable代替终结器(在垃圾回收器释放对象前显式释放资源),它将十分有效率,因为我们的GC一次就能回收掉资源,而不是两次。

  4. 将对象和其子对象放在一起。便于GC拷贝大数据而不是数据碎片,当我们声明一个对象时,尽可能将内部所有对象声明的近一些。

  5. 相关阅读: 译文—C#堆VS栈(Part Four)

猜你喜欢

转载自blog.csdn.net/jingangxin666/article/details/80068219
今日推荐