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框架的主要特点包括:
- 对象映射:将数据库表映射为对象,行映射为对象的属性。
- 数据查询:使用对象编程语言查询数据库,无需手写SQL。
- 数据操作:对对象的增删改查操作可以自动转换为数据库操作。
- 缓存管理:一些ORM框架提供查询结果的缓存管理。
.NET中常用的ORM框架包括:
- Entity Framework:微软官方的ORM框架,支持数据库第一和代码第一的开发模式。
- NHibernate:一个成熟且功能丰富的ORM框架,支持多种.NET版本。
- Dapper:一种轻量级的ORM框架,专注于性能和简单性。
LINQ关键字
LINQ提供了一组扩展方法和查询语法,用于查询集合。以下是一些常用的LINQ关键字和概念:
-
where:用于过滤数据。
var filteredItems = from item in items where item.Condition == true select item;
-
select:用于选择或投影数据。
var projectedItems = from item in items select new { item.Property };
-
from:用于指定查询的数据源。
var query = from customer in customers select customer;
-
join:用于执行连接操作。
var joinedQuery = from order in orders join customer in customers on order.CustomerId equals customer.Id select new { order, customer };
-
group:用于对数据进行分组。
var groupedQuery = from item in items group item by item.Category into groupedItems select new { Category = groupedItems.Key, Items = groupedItems };
-
orderby/orderby descending:用于排序数据。
var orderedQuery = from item in items orderby item.Date descending select item;
-
aggregate operators:如
sum
、average
、min
、max
、count
等,用于聚合操作。int count = items.Count(); int sum = items.Sum(item => item.Value);
-
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.Wait
或 object.Wait
时,当前线程会释放指定对象的锁定,并进入等待状态。其他线程可以通过调用 Monitor.Pulse
或 object.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依赖注入的几种方式
-
Register:这种方式用于注册瞬态(Transient)服务,即每次请求服务时都会创建一个新的实例。
containerRegistry.Register<FooService>(); containerRegistry.Register<IBarService, BarService>();
-
RegisterSingleton:这种方式用于注册单例(Singleton)服务,即在应用程序的整个生命周期内,每次请求服务时都会返回同一个实例。
containerRegistry.RegisterSingleton<FooService>(); containerRegistry.RegisterSingleton<IBarService, BarService>();
-
RegisterScoped:这种方式用于注册作用域(Scoped)服务,即在每个容器作用域内创建一个新的实例,但在特定作用域内保持同一个实例。
containerRegistry.RegisterScoped<FooService>();
依赖注入生命周期
Prism支持三种服务生命周期:
-
Transient:每次请求服务时都会创建一个新的实例。适用于不需要保持状态的服务。
-
Singleton:整个应用程序生命周期内只创建一个实例。适用于需要全局访问点或需要保持状态的服务。
-
Scoped:在每个容器作用域内创建一个新的实例,但在特定作用域内保持同一个实例。这在Web应用程序中常用于请求作用域,但在桌面和移动应用程序中,Prism.Maui会在每个页面周围创建一个作用域,用于
INavigationService
、IPageDialogService
和IDialogService
等服务。
6.触发器有哪些
-
DML触发器:
- INSERT触发器:在向表中插入数据时触发。
- UPDATE触发器:在修改表中数据时触发。
- DELETE触发器:在从表中删除数据时触发。
-
DDL触发器:
- 这类触发器在数据库结构发生变化时触发,如CREATE、ALTER、DROP等事件。
-
登录触发器:
- 在用户登录过程中触发,通常用于身份验证和日志记录。
-
行级触发器:
- 针对表中每一行数据变化触发一次。
-
语句级触发器:
- 针对一次数据操作(如一次INSERT、UPDATE或DELETE语句)触发一次。
-
BEFORE触发器:
- 在触发事件发生之前执行。
-
AFTER触发器:
- 在触发事件发生之后执行。
-
INSTEAD OF触发器:
- 代替触发动作执行,并在处理约束之前激发。
-
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.数据库游标及用处
游标的特点
- 逐行处理:游标允许你逐行访问结果集,这在需要对每行数据进行迭代处理时非常有用。
- 控制操作:你可以控制如何处理结果集中的每一行,例如,更新或删除特定的行。
- 灵活性:游标提供了一种灵活的方式来处理查询结果,尤其是在复杂的业务逻辑中。
游标的用途
- 分批处理:当处理大量数据时,使用游标可以分批处理结果,避免一次性加载过多数据到内存中。
- 复杂计算:在需要对结果集中的数据进行复杂计算或逻辑判断时,游标可以逐行处理数据。
- 更新和删除:游标可以用来定位特定的行,并进行更新或删除操作。
- 报表生成:在生成报表时,游标可以用来逐行处理数据,以便进行格式化输出。
- 数据迁移:在数据迁移过程中,游标可以用来逐行比较和同步数据。
使用游标的例子(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;
注意事项
- 性能:游标可能会影响查询性能,尤其是在处理大量数据时。尽量避免在性能敏感的场景中使用游标。
- 资源消耗:游标可能会占用较多的系统资源,尤其是在长时间运行的事务中。
- 锁定:使用游标时,可能会锁定结果集中涉及的行,这可能会影响并发访问。
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
应用场景:
- 网络通信:网络协议通常使用大端字节序(也称为网络字节序)来保证数据的一致性。
- 文件格式:某些文件格式可能规定了特定的字节序,以确保跨平台的兼容性。
- 硬件接口:不同的硬件平台可能采用不同的字节序,这在进行硬件编程时需要特别注意。
12.为什么设计低位在前,高位在后
-
计算效率:计算机的电路设计通常先处理低位字节,因为计算都是从低位开始的。因此,对于某些特定的计算机体系结构,使用小端字节序可以提高内存访问效率。
-
硬件设计:在某些硬件设计中,数据从低位到高位依次处理更为自然,这与计算机内部处理数据的方式相匹配。
-
网络传输:尽管网络传输通常采用大端字节序(Big Endian),但在某些特定的通信协议中,如I2C,规定了数据传输必须是高位先行,这要求在发送和接收数据时必须遵循协议规定。
-
通用性和兼容性:在设计某些系统时,为了确保与现有系统的兼容性,可能选择使用小端字节序,尤其是在x86架构的CPU中,它们通常使用小端字节序。
-
人类阅读习惯:大端字节序(Big Endian)更符合人类的阅读习惯,因为人们通常从左到右阅读,而左边通常是高位。但在内存中,低地址通常在前,这与小端字节序相匹配。
-
数据表示的直观性:大端存储通常被认为是一种更加直观的存储方式,因为它的字节序与人类通常的阅读顺序一致,这有助于在处理数据和调试时更容易理解内存中的数据表示。