MST
星途 面试题库

面试题:C#泛型类与对象实例化的高级应用

设计一个C#泛型类,该类需要实现对不同类型数据的缓存功能(假设缓存使用字典来存储)。要求提供添加数据到缓存、从缓存获取数据以及处理缓存数据过期等功能。阐述在实例化这个泛型类对象时,如何考虑类型参数的约束,以及在多线程环境下,如何保证对象实例化和缓存操作的线程安全性。
33.3万 热度难度
编程语言C#

知识考点

AI 面试

面试题答案

一键面试
using System;
using System.Collections.Generic;
using System.Threading;

public class CacheItem<TValue>
{
    public TValue Value { get; set; }
    public DateTime ExpiryTime { get; set; }
}

public class GenericCache<TKey, TValue>
{
    private readonly Dictionary<TKey, CacheItem<TValue>> _cache;
    private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();

    public GenericCache()
    {
        _cache = new Dictionary<TKey, CacheItem<TValue>>();
    }

    public void Add(TKey key, TValue value, TimeSpan expirationTime)
    {
        _lock.EnterWriteLock();
        try
        {
            var cacheItem = new CacheItem<TValue>
            {
                Value = value,
                ExpiryTime = DateTime.Now + expirationTime
            };
            _cache[key] = cacheItem;
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public bool TryGet(TKey key, out TValue value)
    {
        _lock.EnterReadLock();
        try
        {
            if (_cache.TryGetValue(key, out var cacheItem) && cacheItem.ExpiryTime > DateTime.Now)
            {
                value = cacheItem.Value;
                return true;
            }
        }
        finally
        {
            _lock.ExitReadLock();
        }
        value = default(TValue);
        return false;
    }

    public void RemoveExpiredItems()
    {
        _lock.EnterWriteLock();
        try
        {
            var keysToRemove = new List<TKey>();
            foreach (var kvp in _cache)
            {
                if (kvp.Value.ExpiryTime < DateTime.Now)
                {
                    keysToRemove.Add(kvp.Key);
                }
            }
            foreach (var key in keysToRemove)
            {
                _cache.Remove(key);
            }
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }
}

类型参数约束的考虑

  1. TKey约束
    • TKey通常应该实现IEquatable<TKey>接口,因为字典需要比较键是否相等。在C#中,大多数内置类型(如intstring等)已经实现了此接口。如果TKey是自定义类型,应该确保该类型正确实现IEquatable<TKey>接口,以保证字典操作的正确性。
    • 也可以考虑TKey实现IComparable<TKey>接口,这样在某些需要对键进行排序的场景下会很有用,虽然在当前简单缓存实现中未用到排序。
  2. TValue约束
    • 一般情况下不需要对TValue进行特殊约束,除非缓存的操作需要TValue类型具备特定的行为或接口。例如,如果需要对缓存的对象进行序列化,TValue类型应该实现ISerializable接口或支持其他序列化机制。

多线程环境下的线程安全性

  1. 对象实例化:在多线程环境下实例化GenericCache<TKey, TValue>对象本身通常是线程安全的,因为实例化过程只涉及内存分配和初始化字段,而C#的对象初始化是原子操作。不过,如果有静态字段或共享资源在对象实例化过程中被访问或修改,就需要特别处理,例如使用lock关键字或Lazy<T>类型进行延迟初始化。
  2. 缓存操作
    • 读写锁机制:使用ReaderWriterLockSlim类来控制对缓存字典的访问。对于读操作(TryGet方法),使用读锁,允许多个线程同时读取缓存,提高并发性能。对于写操作(AddRemoveExpiredItems方法),使用写锁,确保同一时间只有一个线程可以修改缓存,避免数据竞争。
    • 锁的粒度:在实现中,对每个方法使用锁来保护整个缓存操作,保证操作的原子性。这样可以防止在读取或修改缓存数据时,其他线程干扰导致数据不一致。
    • 异常处理:在获取锁后,使用try - finally块确保无论是否发生异常,锁都会被正确释放,防止死锁的发生。