做个一套C#面试题

1.int long float double 分别是几个字节

左到右范围从小到大:byte->short->int->long->float->double

各自所占字节大小:1字节、2字节、4字节、8字节、4字节、8字节

2.System.Object四个公共方法的申明

namespace System
{
    //
    // 摘要:
    //     Supports all classes in the .NET class hierarchy and provides low-level services
    //     to derived classes. This is the ultimate base class of all .NET classes; it is
    //     the root of the type hierarchy.
    public class Object
    {
        //
        // 摘要:
        //     Initializes a new instance of the System.Object class.
        public Object();

        //
        // 摘要:
        //     Allows an object to try to free resources and perform other cleanup operations
        //     before it is reclaimed by garbage collection.
        ~Object();

        //
        // 摘要:
        //     Determines whether the specified object instances are considered equal.
        //
        // 参数:
        //   objA:
        //     The first object to compare.
        //
        //   objB:
        //     The second object to compare.
        //
        // 返回结果:
        //     true if the objects are considered equal; otherwise, false. If both objA and
        //     objB are null, the method returns true.
        public static bool Equals(Object? objA, Object? objB);
        //
        // 摘要:
        //     Determines whether the specified System.Object instances are the same instance.
        //
        // 参数:
        //   objA:
        //     The first object to compare.
        //
        //   objB:
        //     The second object to compare.
        //
        // 返回结果:
        //     true if objA is the same instance as objB or if both are null; otherwise, false.
        public static bool ReferenceEquals(Object? objA, Object? objB);
        //
        // 摘要:
        //     Determines whether the specified object is equal to the current object.
        //
        // 参数:
        //   obj:
        //     The object to compare with the current object.
        //
        // 返回结果:
        //     true if the specified object is equal to the current object; otherwise, false.
        public virtual bool Equals(Object? obj);
        //
        // 摘要:
        //     Serves as the default hash function.
        //
        // 返回结果:
        //     A hash code for the current object.
        public virtual int GetHashCode();
        //
        // 摘要:
        //     Gets the System.Type of the current instance.
        //
        // 返回结果:
        //     The exact runtime type of the current instance.
        public Type GetType();
        //
        // 摘要:
        //     Returns a string that represents the current object.
        //
        // 返回结果:
        //     A string that represents the current object.
        public virtual string? ToString();
        //
        // 摘要:
        //     Creates a shallow copy of the current System.Object.
        //
        // 返回结果:
        //     A shallow copy of the current System.Object.
        protected Object MemberwiseClone();
    }
}

3.自定义类型在什么情况下需要override Equals函数?为什么往往需要同时Override GetHashCode函数

在以下情况下,自定义类型需要重写 Equals 函数:

需要重写 Equals 的情况
值相等逻辑:当你需要定义两个对象的“值相等”逻辑,而不仅仅是引用相等(即默认情况下的相等)。

例如,在比较两个包含相同数据但不同引用的对象时,它们应该被认为是相等的。
集合操作:当你的对象将用于集合(如哈希表、字典、哈希集)中,需要基于值进行比较和操作时。

例如,想要在集合中避免重复对象,或者根据对象值进行查找。
为什么需要同时重写 GetHashCode
重写 Equals 时,通常也需要重写 GetHashCode,原因如下:

哈希表一致性:如果两个对象根据 Equals 方法被认为是相等的,那么它们的哈希码也必须相等,以确保在基于哈希的集合(如哈希表、字典、哈希集)中能够正确工作。

如果 Equals 返回 true,但 GetHashCode 返回不同的值,会导致哈希集合操作(如查找、插入)出现错误。
集合性能:哈希码用于快速查找集合中的对象。一个好的哈希函数能均匀地分布对象,从而提高集合操作的性能。

示例代码
下面是一个自定义类型的示例,它重写了 Equals 和 GetHashCode 方法:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }

    public override bool Equals(object obj)
    {
        if (obj == null || GetType() != obj.GetType())
        {
            return false;
        }

        Person other = (Person)obj;
        return FirstName == other.FirstName && LastName == other.LastName && Age == other.Age;
    }

    public override int GetHashCode()
    {
        // 使用简单的哈希算法结合字段生成哈希码
        return HashCode.Combine(FirstName, LastName, Age);
    }
}

 注意事项
一致性:Equals 和 GetHashCode 方法必须一致。即,如果两个对象是相等的 (Equals 返回 true),它们必须具有相同的哈希码 (GetHashCode 返回相同的值)。
不可变性:尽量保持对象的不可变性(不可变对象),以确保在其生命周期内哈希码不会改变。如果对象的状态变化会导致 Equals 和 GetHashCode 结果变化,可能会导致集合中的一致性问题。
通过重写 Equals 和 GetHashCode,可以确保自定义类型在各种集合操作和比较中能够正确工作

4.下面的代码等效于什么?

using(var a=new MyClasse())
{
doing...
}

using 语句用于确保在代码块执行完毕后正确释放资源。如下所示:

MyClasse a = null;
try
{
    a = new MyClasse();
    // doing...
}
finally
{
    if (a != null)
    {
        a.Dispose();
    }
}

 这种方式确保了即使在发生异常的情况下,也会调用 Dispose 方法来释放资源。

5.值类型与引用类型的内存分配及GC回收时的区别

值类型和引用类型在内存分配和垃圾回收(GC)方面有明显区别:

值类型
内存分配:
值类型通常在栈上分配内存(除非它们是其他对象的一部分,如数组或类的成员)。
分配和释放非常快速,因为栈是按顺序分配和释放内存的。


生命周期:
值类型的生命周期与其作用域紧密相关,当它们超出作用域时,内存自动释放。


引用类型
内存分配:
引用类型在堆上分配内存。
堆内存分配较为复杂,可能导致内存碎片化。


生命周期:
引用类型的对象在不再被引用时不会立即释放。
需要垃圾回收器(GC)来检测和回收不再使用的对象。


GC回收
值类型:

通常不需要GC,因为值类型在栈上分配,超出作用域后自动释放。
引用类型:

GC会在堆上检测不再使用的对象并回收内存。
GC分代机制:
第0代:新分配的对象。
第1代:从第0代晋升的对象,存活时间较长。
第2代:长期存活的对象。
GC回收分为几代以优化性能,常用的策略包括标记-清除、标记-压缩、复制算法。
总结:

值类型在栈上分配,生命周期由作用域决定,通常不需要GC。
引用类型在堆上分配,生命周期由引用计数决定,需要GC来管理内存。

6.函数传递中值类型及引用类型的区别

在函数传递中,值类型和引用类型的区别主要体现在参数传递方式及其对原始数据的影响上。

值类型传递
传递方式:值类型参数是按值传递的。调用方法时,会将参数的副本传递给方法。
影响:在方法内部对参数的任何修改不会影响原始数据。

public void ModifyValue(int x)
{
    x = 10;
}

int originalValue = 5;
ModifyValue(originalValue);
Console.WriteLine(originalValue); // 输出:5

在这个例子中,originalValue的值不会受到ModifyValue方法中对x的修改影响,因为传递的是originalValue的副本。

引用类型传递
传递方式:引用类型参数是按引用传递的。调用方法时,会将对象的引用传递给方法,而不是对象的副本。
影响:在方法内部对参数的修改会影响原始对象。

public void ModifyReference(MyClass obj)
{
    obj.Value = 10;
}

public class MyClass
{
    public int Value { get; set; }
}

MyClass originalObject = new MyClass();
originalObject.Value = 5;
ModifyReference(originalObject);
Console.WriteLine(originalObject.Value); // 输出:10

 在这个例子中,originalObjectValue属性会受到ModifyReference方法中对obj的修改影响,因为传递的是originalObject的引用。

public void ModifyValue(ref int x)
{
    x = 10;
}

int originalValue = 5;
ModifyValue(ref originalValue);
Console.WriteLine(originalValue); // 输出:10

out 关键字:类似于ref,但通常用于方法需要返回多个值的情况,且参数在传入方法前不需要初始化。

示例:

public void InitializeValue(out int x)
{
    x = 10;
}

int originalValue;
InitializeValue(out originalValue);
Console.WriteLine(originalValue); // 输出:10

 总结
值类型:按值传递,传递副本,方法内部修改不影响原始数据。
引用类型:按引用传递,传递引用,方法内部修改影响原始对象。
ref 和 out:可以使值类型按引用传递,使方法内部修改影响原始数据。

7.说明一些特性Attribute是什么?如何使用自定义的Attribute?

什么是特性(Attribute)
特性(Attribute)是 .NET 中用于向程序元素(如类、方法、属性等)添加元数据的一种方法。特性在运行时可以通过反射机制获取,用于提供关于程序元素的额外信息,控制程序行为,或为工具提供指示。

常见的内置特性
[Obsolete]:标记某个程序元素为过时。
[Serializable]:标记某个类可以序列化。
[DllImport]:用于引入外部的非托管代码。
[TestMethod]:在单元测试框架中标记一个方法为测试方法。
如何使用自定义特性
定义自定义特性: 自定义特性需要继承自 System.Attribute 类。可以为特性添加构造函数和属性来传递数据。

应用自定义特性: 将自定义特性应用到目标程序元素上。

通过反射读取特性: 在运行时使用反射来获取和处理这些特性。

示例
1. 定义自定义特性

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class MyCustomAttribute : Attribute
{
    public string Description { get; }
    public int Version { get; }

    public MyCustomAttribute(string description, int version)
    {
        Description = description;
        Version = version;
    }
}

2. 应用自定义特性

[MyCustomAttribute("This is a sample class", 1)]
public class SampleClass
{
    [MyCustomAttribute("This is a sample method", 2)]
    public void SampleMethod()
    {
        // Method implementation
    }
}

 3. 通过反射读取特性

using System;
using System.Reflection;

public class Program
{
    public static void Main()
    {
        Type type = typeof(SampleClass);

        // 获取类上的自定义特性
        object[] classAttributes = type.GetCustomAttributes(typeof(MyCustomAttribute), false);
        foreach (MyCustomAttribute attr in classAttributes)
        {
            Console.WriteLine($"Class Attribute - Description: {attr.Description}, Version: {attr.Version}");
        }

        // 获取方法上的自定义特性
        MethodInfo method = type.GetMethod("SampleMethod");
        object[] methodAttributes = method.GetCustomAttributes(typeof(MyCustomAttribute), false);
        foreach (MyCustomAttribute attr in methodAttributes)
        {
            Console.WriteLine($"Method Attribute - Description: {attr.Description}, Version: {attr.Version}");
        }
    }
}

总结
定义自定义特性:通过继承 System.Attribute 并定义构造函数和属性。
应用自定义特性:将特性应用到类、方法或其他程序元素上。
读取自定义特性:通过反射获取特性并处理其中包含的信息。
使用自定义特性可以为代码添加额外的语义信息,便于维护和工具化操作。 

8.String类与StringBuilder类有什么区别?请说明一些什么场景下字符串拼接需要StringBuilder?

String 类和 StringBuilder 类都是 .NET 中用于处理字符串的类,但它们在实现和使用场景上有显著的区别。

String 类
不可变性:String 对象是不可变的。一旦创建,字符串的值就无法更改。任何对字符串进行操作的方法都会创建一个新的字符串对象。
内存和性能:由于不可变性,频繁的字符串操作(如拼接、插入、删除)会导致大量临时对象的创建和垃圾回收,进而影响性能。
StringBuilder 类
可变性:StringBuilder 对象是可变的。它允许在不创建新对象的情况下修改内容,如追加、插入、删除等。
内存和性能:StringBuilder 在进行大量字符串操作时表现出更高的性能,因为它避免了创建大量临时对象。
使用场景
使用 String 的场景
字符串内容较少或字符串操作不频繁的情况下。
需要线程安全的操作,因为 String 是不可变的,因此本质上是线程安全的。
使用 StringBuilder 的场景
需要进行大量字符串拼接或频繁的字符串操作时,例如在循环中构建字符串内容。
对性能要求较高的情况下,避免大量临时对象的创建和垃圾回收。
示例代码
使用 String

public string ConcatenateWithString()
{
    string result = "Start";
    for (int i = 0; i < 1000; i++)
    {
        result += i.ToString();
    }
    return result;
}

上述代码中,每次循环都会创建一个新的字符串对象,导致性能较低。

使用 StringBuilder 

using System.Text;

public string ConcatenateWithStringBuilder()
{
    StringBuilder sb = new StringBuilder("Start");
    for (int i = 0; i < 1000; i++)
    {
        sb.Append(i);
    }
    return sb.ToString();
}

上述代码中,StringBuilder 对象在循环中不断地追加字符串,而不会创建新的对象,因此性能较高。

总结
String:适用于少量字符串操作和需要线程安全的场景。
StringBuilder:适用于大量字符串操作和对性能要求较高的场景。
在实际开发中,选择 String 还是 StringBuilder 需要根据具体情况进行权衡。如果操作次数较少且对性能影响不大,使用 String 更加直观和简单;而在需要频繁修改字符串内容的场景下,StringBuilder 则是更好的选择。

 

9.委托与事件

委托和事件是 .NET 中用于处理方法调用和事件通知的机制。它们有许多相似之处,但也有重要的区别。下面是关于委托和事件的详细说明。

委托
定义
委托是一种类型安全的函数指针,允许你将方法作为参数传递。委托可以指向一个或多个方法,并在需要时调用这些方法。

示例

// 定义一个委托类型
public delegate void MyDelegate(string message);

public class DelegateExample
{
    // 定义一个方法,符合委托类型
    public void DisplayMessage(string message)
    {
        Console.WriteLine(message);
    }

    public void TestDelegate()
    {
        // 创建委托实例并绑定方法
        MyDelegate del = DisplayMessage;
        // 调用委托
        del("Hello, Delegates!");
    }
}

特性
可以指向一个或多个方法(多播委托)。
可以作为参数传递给方法,或者从方法返回。
允许灵活地调用方法。
事件
定义
事件是委托的封装,用于实现发布/订阅模式。事件提供了一种安全的方式,允许对象向其他对象通知状态的变化,而不需要这些对象之间有紧密耦合。

示例

// 定义一个委托类型,用于事件处理程序
public delegate void MyEventHandler(object sender, EventArgs e);

public class EventExample
{
    // 定义一个事件
    public event MyEventHandler MyEvent;

    // 触发事件的方法
    protected virtual void OnMyEvent(EventArgs e)
    {
        if (MyEvent != null)
        {
            MyEvent(this, e);
        }
    }

    public void TestEvent()
    {
        // 订阅事件
        MyEvent += EventHandlerMethod;
        // 触发事件
        OnMyEvent(EventArgs.Empty);
    }

    // 事件处理方法
    private void EventHandlerMethod(object sender, EventArgs e)
    {
        Console.WriteLine("Event triggered!");
    }
}

 

特性
封装委托,提供更安全和受控制的方法来处理事件通知。
只能在定义事件的类或结构内部触发(调用)。
提供了发布/订阅模式,允许对象订阅感兴趣的事件。
委托和事件的区别
定义和使用:

委托:可以直接调用和赋值,灵活性较高。
事件:基于委托,提供了更严格的访问控制,通常用来通知状态变化。
访问控制:

委托:任何类都可以调用委托。
事件:只能在声明事件的类或结构内部触发,外部只能订阅(+=)或取消订阅(-=)。
应用场景:

委托:适用于需要将方法作为参数传递的场景,或需要多播委托的场景。
事件:适用于发布/订阅模式,需要通知多个订阅者状态变化的场景。
选择使用
委托:当你需要将方法作为参数传递,或需要一个函数指针来调用多个方法时,使用委托。
事件:当你需要实现观察者模式,需要一个对象通知其他对象状态变化时,使用事件。
通过了解委托和事件的区别和使用场景,可以更好地设计和实现灵活、可扩展的应用程序。

10装箱和拆箱以及它对程序的性能有什么影响以及怎样避免装箱和拆箱

装箱和拆箱是 .NET 中值类型和引用类型之间的转换过程。理解它们对程序性能的影响非常重要。

装箱(Boxing)
定义:将值类型转换为引用类型的过程。具体来说,是将一个值类型的值封装在一个 System.Object 类型或其他接口类型的实例中。
过程:在堆上分配内存,并将值类型的值复制到新分配的对象中。
示例:

int value = 123;
object boxedValue = value; // 装箱

拆箱(Unboxing)
定义:将引用类型转换回值类型的过程。具体来说,是从对象中提取值类型的值。
过程:从堆上的对象中提取值类型的值,并将其复制到栈上。
示例:

object boxedValue = 123;
int value = (int)boxedValue; // 拆箱

 

对性能的影响
内存分配:装箱会在堆上分配内存,增加垃圾回收的负担。频繁的装箱和拆箱会导致内存碎片化,增加垃圾回收器的工作量。
性能开销:装箱和拆箱涉及内存分配和复制操作,这些操作是耗时的。频繁的装箱和拆箱会显著降低程序的性能。
如何避免装箱和拆箱
使用泛型:泛型可以避免装箱和拆箱,因为它们在编译时确定类型,而不是在运行时。

示例:

List<int> list = new List<int>(); // 使用泛型集合避免装箱
list.Add(123);

避免将值类型存储在 object 或接口类型的变量中:尽量避免将值类型赋值给 object 类型或接口类型的变量。

示例:

int value = 123;
// object obj = value; // 避免这样做

 使用适当的集合类型:使用专门处理值类型的集合,例如 List<T>、Dictionary<TKey, TValue> 等,而不是非泛型集合如 ArrayList。

示例:

ArrayList arrayList = new ArrayList();
// arrayList.Add(123); // 避免这样做

List<int> list = new List<int>();
list.Add(123); // 使用泛型集合

使用 struct 而不是 class:在定义需要频繁分配和释放的对象时,可以考虑使用 struct 而不是 class,以减少装箱和拆箱的需求。

示例:

struct MyStruct
{
    public int Value;
}

MyStruct myStruct = new MyStruct();
myStruct.Value = 123;

 总结
装箱:值类型转换为引用类型,会在堆上分配内存,增加垃圾回收负担。
拆箱:引用类型转换为值类型,从堆中提取值类型的值。
性能影响:装箱和拆箱会导致内存分配和复制操作,频繁的装箱和拆箱会显著降低程序性能。
避免方法:使用泛型、避免将值类型存储在 object 或接口类型中、使用适当的集合类型、考虑使用 struct。
通过理解和避免不必要的装箱和拆箱,可以显著提高程序的性能和效率。

11单例模式的应用场景并实现一个单例模式,Singleton非静态类

单例模式用于确保一个类只有一个实例,并提供全局访问点。常见应用场景包括:

  1. 配置管理类:如数据库配置、日志管理器等。
  2. 资源访问类:如文件系统访问、网络连接等。
  3. 全局缓存:如应用程序缓存、线程池等。 

实现一个非静态类的单例模式: 

public class Singleton {
    // 私有静态实例,防止被直接访问
    private static Singleton instance;

    // 私有构造方法,防止被实例化
    private Singleton() {
    }

    // 公有静态方法,返回唯一实例
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    // 其他方法
    public void doSomething() {
        // 实现功能
    }
}

 12.此截图中的结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值