Bootstrap

C#中面试的常见问题005

1、重载和重写

重载(Overloading)

重载是指在同一个类中定义多个同名方法,但参数列表不同(参数的数量、类型或顺序不同)。返回类型可以相同也可以不同。重载方法允许你根据传入的参数类型和数量来调用不同的方法。

特点

  • 方法名相同,但参数列表不同。
  • 返回类型可以不同。
  • 编译器根据方法签名(方法名和参数列表)来区分不同的重载方法。

示例

public class Calculator
{
    // 重载方法:加法
    public int Add(int a, int b)
    {
        return a + b;
    }

    // 重载方法:加法,参数为double类型
    public double Add(double a, double b)
    {
        return a + b;
    }
}

重写(Overriding)

重写是指在派生类(子类)中重新实现基类中的虚方法(virtual method)。重写允许派生类提供特定的实现,以改变从基类继承来的行为。

特点

  • 方法名和参数列表必须与基类中的虚方法完全相同。
  • 返回类型必须与基类中的虚方法相同。
  • 访问修饰符不能比基类方法的访问修饰符更严格。
  • 必须使用override关键字来明确表示重写。

示例

public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("Some sound");
    }
}

public class Dog : Animal
{
    // 重写基类中的Speak方法
    public override void Speak()
    {
        Console.WriteLine("Bark");
    }
}

区别

  • 目的不同:重载用于在同一个类中定义多个同名方法,参数不同;重写用于在派生类中改变从基类继承来的方法的行为。
  • 位置不同:重载发生在同一个类中;重写发生在派生类中。
  • 规则不同:重载方法的参数列表必须不同;重写方法的参数列表和返回类型必须与基类中的虚方法相同。
  • 关键字不同:重写使用override关键字,而重载不需要。

2.ORM框架和Linq关键字

ORM框架

ORM框架的主要特点包括:

  1. 对象映射:将数据库表映射为对象,行映射为对象的属性。
  2. 数据查询:使用对象编程语言查询数据库,无需手写SQL。
  3. 数据操作:对对象的增删改查操作可以自动转换为数据库操作。
  4. 缓存管理:一些ORM框架提供查询结果的缓存管理。

.NET中常用的ORM框架包括:

  • Entity Framework:微软官方的ORM框架,支持数据库第一和代码第一的开发模式。
  • NHibernate:一个成熟且功能丰富的ORM框架,支持多种.NET版本。
  • Dapper:一种轻量级的ORM框架,专注于性能和简单性。

LINQ关键字

LINQ提供了一组扩展方法和查询语法,用于查询集合。以下是一些常用的LINQ关键字和概念:

  1. where:用于过滤数据。

    var filteredItems = from item in items where item.Condition == true select item;
  2. select:用于选择或投影数据。

    var projectedItems = from item in items select new { item.Property };
  3. from:用于指定查询的数据源。

    var query = from customer in customers select customer;
  4. join:用于执行连接操作。

    var joinedQuery = from order in orders join customer in customers on order.CustomerId equals customer.Id select new { order, customer };
  5. group:用于对数据进行分组。

    var groupedQuery = from item in items group item by item.Category into groupedItems select new { Category = groupedItems.Key, Items = groupedItems };
  6. orderby/orderby descending:用于排序数据。

    var orderedQuery = from item in items orderby item.Date descending select item;
  7. aggregate operators:如sumaverageminmaxcount等,用于聚合操作。

    int count = items.Count();
    int sum = items.Sum(item => item.Value);
  8. let:用于为查询中的子句引入一个中间变量。

    var query = from item in items let size = item.Size where size > 10 select new { item, size };

LINQ和ORM框架的结合使用,使得开发者可以以声明式的方式处理数据库操作,提高了代码的可读性和维护性。例如,Entity Framework使用LINQ作为其查询语言,允许开发者编写如下代码:

using (var context = new MyDbContext())
{
    var customers = context.Customers
                           .Where(c => c.IsActive)
                           .OrderBy(c => c.Name)
                           .ToList();
}

3.多线程,Sleep和wait的区别

Thread.Sleep

Thread.Sleep 是一个静态方法,它属于 System.Threading 命名空间。当调用 Thread.Sleep 时,当前线程会暂停执行指定的时间量,让出CPU给其他线程使用。

特点

  • Thread.Sleep 会使当前线程挂起,但不释放任何锁。
  • 它不会释放任何对象的锁定;如果当前线程持有一个或多个锁,这些锁在 Sleep 期间仍然保持。
  • Thread.Sleep 不能被中断,除非睡眠时间结束或者线程被中止。

示例

Thread.Sleep(1000); // 使当前线程暂停1000毫秒(1秒)

Monitor.Wait 和 Object.Wait

Monitor.Wait 是一个方法,它属于 System.Threading 命名空间,用于在同步锁定代码块或方法中等待某个条件。当调用 Monitor.Waitobject.Wait 时,当前线程会释放指定对象的锁定,并进入等待状态。其他线程可以通过调用 Monitor.Pulseobject.Pulse 来唤醒等待的线程。

特点

  • Monitor.Wait 和 object.Wait 会使当前线程等待,直到被 Pulse 或 PulseAll 唤醒,或者超时。
  • 它们通常与 lock 语句一起使用,以实现线程间的同步。
  • Wait 方法在进入等待状态前会释放对象的锁定,允许其他线程进入同步块。
  • Wait 可以设置超时,使线程在指定的等待时间后继续执行。

示例

object lockObject = new object();
bool condition = false;

void ThreadMethod()
{
    lock (lockObject)
    {
        // 等待条件变为true
        while (!condition)
        {
            Monitor.Wait(lockObject);
        }
        // 条件满足,执行后续操作
    }
}

// 在另一个线程中
lock (lockObject)
{
    condition = true;
    Monitor.Pulse(lockObject); // 唤醒等待的线程
}

区别

  • 用途Thread.Sleep 用于暂停线程执行,而 Monitor.Wait 和 object.Wait 用于线程间的同步和协调。
  • Thread.Sleep 不释放锁,而 Monitor.Wait 和 object.Wait 在等待前释放锁。
  • 唤醒Thread.Sleep 无法被外部操作唤醒,只能自然醒来或被中止;Monitor.Wait 和 object.Wait 可以被 Pulse 或 PulseAll 唤醒。
  • 超时Monitor.Wait 和 object.Wait 可以设置超时,Thread.Sleep 不能。

4.三层架构,使用它的好处

1. 低耦合性

  • 三层架构通过将功能划分为不同的层,使得各层之间的耦合性降低,便于单独修改和维护。

2. 高内聚性

  • 每一层都具有特定的职责,内聚性高,代码更加模块化。

3. 易于测试

  • 由于层与层之间的接口明确,可以单独对业务逻辑层和数据访问层进行单元测试,提高测试的覆盖率和质量。

4. 重用性

  • 业务逻辑层和数据访问层可以被多个表示层重用,提高了代码的重用性。

5. 可维护性

  • 由于分层清晰,新的开发人员可以更快地理解和维护代码。

6. 可扩展性

  • 可以根据需求独立扩展各层,例如,在不影响业务逻辑层的情况下,更换数据访问层的实现。

7. 分离关注点

  • 开发者可以专注于单个层的开发,分离了用户界面、业务规则和数据访问的关注点。

8. 安全性

  • 通过在表示层和业务逻辑层之间增加安全控制,可以更好地保护数据和业务逻辑。

9. 适应变化

  • 业务需求变化时,可以快速调整业务逻辑层或表示层,而不需要修改数据访问层。

10. 技术多样性

  • 团队可以使用不同的技术栈来开发不同的层,例如,使用ASP.NET MVC作为表示层,C#作为业务逻辑层,Entity Framework作为数据访问层。

11. 性能优化

  • 可以根据性能需求对各层进行优化,例如,在数据访问层实现缓存策略。

12. 部署灵活性

  • 可以独立部署各层,例如,在不同的服务器上部署表示层和业务逻辑层,以满足不同的负载需求。

5.Prism依赖注入的几种方式?依赖注入生命周期

Prism依赖注入的几种方式

  1. Register:这种方式用于注册瞬态(Transient)服务,即每次请求服务时都会创建一个新的实例。

    containerRegistry.Register<FooService>();
    containerRegistry.Register<IBarService, BarService>();
  2. RegisterSingleton:这种方式用于注册单例(Singleton)服务,即在应用程序的整个生命周期内,每次请求服务时都会返回同一个实例。

    containerRegistry.RegisterSingleton<FooService>();
    containerRegistry.RegisterSingleton<IBarService, BarService>();
  3. RegisterScoped:这种方式用于注册作用域(Scoped)服务,即在每个容器作用域内创建一个新的实例,但在特定作用域内保持同一个实例。

    containerRegistry.RegisterScoped<FooService>();

依赖注入生命周期

Prism支持三种服务生命周期:

  1. Transient:每次请求服务时都会创建一个新的实例。适用于不需要保持状态的服务。

  2. Singleton:整个应用程序生命周期内只创建一个实例。适用于需要全局访问点或需要保持状态的服务。

  3. Scoped:在每个容器作用域内创建一个新的实例,但在特定作用域内保持同一个实例。这在Web应用程序中常用于请求作用域,但在桌面和移动应用程序中,Prism.Maui会在每个页面周围创建一个作用域,用于INavigationServiceIPageDialogServiceIDialogService等服务。

6.触发器有哪些

  1. DML触发器

    • INSERT触发器:在向表中插入数据时触发。
    • UPDATE触发器:在修改表中数据时触发。
    • DELETE触发器:在从表中删除数据时触发。
  2. DDL触发器

    • 这类触发器在数据库结构发生变化时触发,如CREATE、ALTER、DROP等事件。
  3. 登录触发器

    • 在用户登录过程中触发,通常用于身份验证和日志记录。
  4. 行级触发器

    • 针对表中每一行数据变化触发一次。
  5. 语句级触发器

    • 针对一次数据操作(如一次INSERT、UPDATE或DELETE语句)触发一次。
  6. BEFORE触发器

    • 在触发事件发生之前执行。
  7. AFTER触发器

    • 在触发事件发生之后执行。
  8. INSTEAD OF触发器

    • 代替触发动作执行,并在处理约束之前激发。
  9. CLR触发器

    • 可以是AFTER触发器或INSTEAD OF触发器,执行在托管代码中编写的方法,而不用执行Transact-SQL存储过程。

7.Socket心跳

1. 客户端主动发送心跳

客户端定期主动向服务器发送心跳包,服务器收到心跳包后回复确认。如果服务器在一定时间内没有收到心跳包,就会认为客户端已经断开连接。这种方式对服务器性能要求不高,适用于服务器性能有限的场景。

2. 服务器主动发送心跳

服务器建立定时器,定时发送心跳包给客户端,客户端收到后立即回复。如果服务器在规定时间内没有收到客户端的回复,就认为客户端连接不可用,执行释放socket操作。这种方式对服务器性能要求较高。

3. 使用SO_KEEPALIVE套接字选项

在Linux系统中,可以通过设置SO_KEEPALIVE套接字选项来启用TCP层的心跳机制。这需要在Socket选项中设置几个参数:TCP_KEEPIDLE(空闲时间)、TCP_KEEPINTVL(心跳间隔)和TCP_KEEPCNT(最大心跳次数)。如果超过空闲时间没有数据传输,系统会自动发送心跳包,如果在指定的心跳次数内没有收到响应,系统会认为连接已经断开。

4. 应用层自实现心跳

应用程序自己发送心跳包来检测连接是否正常。服务器每隔一定时间向客户端发送一个短小的数据包,然后启动一个线程,在线程中不断检测客户端的回应。如果在一定时间内没有收到客户端的回应,即认为客户端已经掉线;同样,如果客户端在一定时间内没有收到服务器的心跳包,则认为连接不可用。

心跳的作用

  • 通知服务器客户端的存活状态,防止服务器在客户端长时间无活动后释放资源。
  • 定时刷新NAT内外网IP映射表,防止NAT路由器移除映射表导致连接中断,影响用户体验。

8.数据库除增删改查外还会什么操作

1. 数据聚合(Aggregation)

使用聚合函数(如SUM, AVG, MAX, MIN, COUNT)对数据进行汇总计算。

SELECT COUNT(*), AVG(salary) FROM employees;

2. 数据分组(Grouping)

使用GROUP BY子句对结果集进行分组,通常与聚合函数一起使用。

SELECT department_id, SUM(salary) FROM employees GROUP BY department_id;

3. 数据排序(Sorting)

使用ORDER BY子句对查询结果进行排序。

SELECT * FROM customers ORDER BY last_name ASC, first_name DESC;

4. 数据连接(Joining)

使用JOIN子句连接多个表,以合并来自不同表的数据。

SELECT customers.name, orders.order_id FROM customers JOIN orders ON customers.customer_id = orders.customer_id;

5. 子查询(Subqueries)

在查询中嵌套另一个查询,用于复杂的数据检索。

SELECT * FROM employees WHERE salary > (SELECT AVG(salary) FROM employees);

6. 事务管理(Transaction Management)

控制事务的开始、提交和回滚,确保数据的一致性和完整性。

BEGIN TRANSACTION;
-- 一系列数据库操作
COMMIT; -- 或 ROLLBACK;

7. 索引管理(Index Management)

创建和管理索引,以优化查询性能。

CREATE INDEX idx_lastname ON customers(last_name);

8. 视图创建(View Creation)

创建视图,作为查询结果的虚拟表。

CREATE VIEW high_earners AS SELECT * FROM employees WHERE salary > 100000;

9. 数据定义语言(DDL)操作

包括创建、修改和删除数据库对象(如表、视图、索引、触发器等)。

CREATE TABLE new_table (column1 INT, column2 VARCHAR(255));
ALTER TABLE existing_table ADD new_column INT;
DROP TABLE obsolete_table;

10. 数据备份与恢复(Backup and Restore)

备份数据库以防数据丢失,并在需要时恢复数据。

-- 备份操作依赖于具体的数据库管理系统
BACKUP DATABASE myDatabase TO DISK = 'backup.bak';
-- 恢复操作
RESTORE DATABASE myDatabase FROM DISK = 'backup.bak';

11. 数据库权限管理(Privilege Management)

设置和管理用户权限,控制对数据库对象的访问。

GRANT SELECT, INSERT ON employees TO new_user;
REVOKE UPDATE ON employees FROM another_user;

12. 数据库监控和优化(Monitoring and Optimization)

监控数据库性能,分析查询计划,优化查询和数据库结构。

13. 数据库迁移(Database Migration)

将数据从一个数据库迁移到另一个数据库,可能涉及不同的数据库系统。

9.排序的关键字、降序关键字、默认排序、分组的关键字

排序的关键字:ORDER BY

ORDER BY 是用于对查询结果进行排序的关键字。

SELECT column1, column2
FROM table_name
ORDER BY column1, column2;

降序关键字:DESC

DESC(Descending 的缩写)关键字用于指定排序顺序为降序,即从大到小。

SELECT column1, column2
FROM table_name
ORDER BY column1 DESC, column2 DESC;

默认排序:ASC

ASC(Ascending 的缩写)关键字用于指定排序顺序为升序,即从小到大。这是默认的排序顺序,即使不写 ASC,SQL也会按照升序排序。

SELECT column1, column2
FROM table_name
ORDER BY column1 ASC, column2 ASC;

分组的关键字:GROUP BY

GROUP BY 是用于将结果集按照一个或多个列的值进行分组的关键字,通常与聚合函数一起使用。

SELECT column1, COUNT(*)
FROM table_name
GROUP BY column1;

聚合函数

聚合函数用于对分组后的数据进行计算,如 SUM(), AVG(), MAX(), MIN(), COUNT() 等。

SELECT column1, SUM(column2), AVG(column2), MAX(column2), MIN(column2), COUNT(*)
FROM table_name
GROUP BY column1;

过滤分组结果:HAVING

HAVING 关键字用于对分组后的结果进行过滤,类似于 WHERE 用于过滤行。

SELECT column1, COUNT(*)
FROM table_name
GROUP BY column1
HAVING COUNT(*) > 1;

10.数据库游标及用处

游标的特点

  1. 逐行处理:游标允许你逐行访问结果集,这在需要对每行数据进行迭代处理时非常有用。
  2. 控制操作:你可以控制如何处理结果集中的每一行,例如,更新或删除特定的行。
  3. 灵活性:游标提供了一种灵活的方式来处理查询结果,尤其是在复杂的业务逻辑中。

游标的用途

  1. 分批处理:当处理大量数据时,使用游标可以分批处理结果,避免一次性加载过多数据到内存中。
  2. 复杂计算:在需要对结果集中的数据进行复杂计算或逻辑判断时,游标可以逐行处理数据。
  3. 更新和删除:游标可以用来定位特定的行,并进行更新或删除操作。
  4. 报表生成:在生成报表时,游标可以用来逐行处理数据,以便进行格式化输出。
  5. 数据迁移:在数据迁移过程中,游标可以用来逐行比较和同步数据。

使用游标的例子(SQL Server)

-- 声明游标
DECLARE my_cursor CURSOR FOR
SELECT column1, column2
FROM table_name
WHERE condition;

-- 打开游标
OPEN my_cursor;

-- 从游标中提取数据
FETCH NEXT FROM my_cursor INTO @variable1, @variable2;

-- 循环处理
WHILE @@FETCH_STATUS = 0
BEGIN
    -- 处理逻辑
    -- 例如:更新数据
    UPDATE table_name SET column1 = @variable1 WHERE CURRENT OF my_cursor;

    -- 提取下一行数据
    FETCH NEXT FROM my_cursor INTO @variable1, @variable2;
END

-- 关闭游标
CLOSE my_cursor;

-- 释放游标
DEALLOCATE my_cursor;

注意事项

  1. 性能:游标可能会影响查询性能,尤其是在处理大量数据时。尽量避免在性能敏感的场景中使用游标。
  2. 资源消耗:游标可能会占用较多的系统资源,尤其是在长时间运行的事务中。
  3. 锁定:使用游标时,可能会锁定结果集中涉及的行,这可能会影响并发访问。

11.什么是低位在前,高位在后

在二进制数中的表示:

在二进制数中,"低位在前,高位在后"意味着最右边的数字(最低位)是最不重要的位(LSB,Least Significant Bit),而最左边的数字(最高位)是最重要的位(MSB,Most Significant Bit)。

例如,对于一个8位的二进制数 01011100

  • 低位在前:00(LSB)1101(MSB)
  • 高位在后:1101(MSB)00(LSB)

在计算机内存中的字节序:

在计算机内存中,"低位在前,高位在后"的概念也与字节序(Byte Order)有关。字节序决定了多字节数据类型(如整数、浮点数等)在内存中的存储顺序。

  • 小端字节序(Little-Endian):低位字节存储在低地址处,高位字节存储在高地址处。这意味着最低位字节(LSB)排在最前面,最高位字节(MSB)排在后面。

    例如,整数 0x12345678 在小端字节序中的存储方式为:

    地址  数据
    0x00   78
    0x01   56
    0x02   34
    0x03   12
  • 大端字节序(Big-Endian):高位字节存储在低地址处,低位字节存储在高地址处。这意味着最高位字节(MSB)排在最前面,最低位字节(LSB)排在后面。

    例如,同一个整数 0x12345678 在大端字节序中的存储方式为:

    地址  数据
    0x00   12
    0x01   34
    0x02   56
    0x03   78

应用场景:

  1. 网络通信:网络协议通常使用大端字节序(也称为网络字节序)来保证数据的一致性。
  2. 文件格式:某些文件格式可能规定了特定的字节序,以确保跨平台的兼容性。
  3. 硬件接口:不同的硬件平台可能采用不同的字节序,这在进行硬件编程时需要特别注意。

12.为什么设计低位在前,高位在后

  1. 计算效率:计算机的电路设计通常先处理低位字节,因为计算都是从低位开始的。因此,对于某些特定的计算机体系结构,使用小端字节序可以提高内存访问效率。

  2. 硬件设计:在某些硬件设计中,数据从低位到高位依次处理更为自然,这与计算机内部处理数据的方式相匹配。

  3. 网络传输:尽管网络传输通常采用大端字节序(Big Endian),但在某些特定的通信协议中,如I2C,规定了数据传输必须是高位先行,这要求在发送和接收数据时必须遵循协议规定。

  4. 通用性和兼容性:在设计某些系统时,为了确保与现有系统的兼容性,可能选择使用小端字节序,尤其是在x86架构的CPU中,它们通常使用小端字节序。

  5. 人类阅读习惯:大端字节序(Big Endian)更符合人类的阅读习惯,因为人们通常从左到右阅读,而左边通常是高位。但在内存中,低地址通常在前,这与小端字节序相匹配。

  6. 数据表示的直观性:大端存储通常被认为是一种更加直观的存储方式,因为它的字节序与人类通常的阅读顺序一致,这有助于在处理数据和调试时更容易理解内存中的数据表示。

;