You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#泛型接口调用优化:无需显式指定双泛型参数

解决泛型处理器的自动类型推导与游戏事件命令模式实现

问题背景

需要存储一批返回类型相同、参数类型不同的处理器对象,调用其方法时避免冗余代码。现有代码结构如下,存在多个IGenericBase<T>的实现(每个实现对应固定的参数类型T):

public interface IBase
{
    
}

public interface IGenericBase<in T> : IBase
{
    int GetNumber(T args);
}

public interface IConcrete : IGenericBase<decimal>
{
    
}

public class Concrete : IConcrete
{
    public int GetNumber(decimal args)
    {
        return (int) args;
    }
}

核心诉求:

  • 调用时无需在GetNumbers<T1, T2>(T2 args)中显式指定两个泛型参数,希望通过处理器类型T1自动推导其对应的参数类型T2(T2在T1的实现中已固定)。
  • 最终基于此实现游戏事件命令模式:当特定游戏事件(如OnTurn、OnCardPlayed)触发时,传入对应事件数据,自动匹配并执行对应的处理器生成命令。

解决方案

1. 通用调用工具类(实现自动类型推导)

通过反射提取处理器实现的泛型接口参数类型,封装通用调用方法,避免手动指定泛型参数:

public static class ProcessorInvoker
{
    // 弱类型调用:适用于通过容器匹配处理器的场景
    public static int InvokeGetNumber<TProcessor>(TProcessor processor, object args)
        where TProcessor : IBase
    {
        var genericBaseType = typeof(TProcessor).GetInterfaces()
            .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IGenericBase<>));
        
        if (genericBaseType == null)
            throw new InvalidOperationException($"{typeof(TProcessor).Name}未实现IGenericBase<T>接口");
        
        var argType = genericBaseType.GetGenericArguments()[0];
        var typedArgs = Convert.ChangeType(args, argType);
        var method = genericBaseType.GetMethod(nameof(IGenericBase<object>.GetNumber));
        return (int)method.Invoke(processor, new[] { typedArgs });
    }

    // 强类型调用:编译器自动推导泛型参数,无需手动指定
    public static int InvokeGetNumber<TProcessor, TArgs>(TProcessor processor, TArgs args)
        where TProcessor : IGenericBase<TArgs>
    {
        return processor.GetNumber(args);
    }
}

2. 处理器容器(适配事件自动匹配)

创建容器按事件数据类型注册处理器,事件触发时直接根据数据类型找到对应处理器执行:

public class ProcessorContainer
{
    private readonly Dictionary<Type, IBase> _processors = new Dictionary<Type, IBase>();

    // 自动识别处理器对应的事件数据类型并注册
    public void Register<TProcessor>(TProcessor processor)
        where TProcessor : IBase
    {
        var genericBaseType = typeof(TProcessor).GetInterfaces()
            .First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IGenericBase<>));
        var eventDataType = genericBaseType.GetGenericArguments()[0];
        _processors[eventDataType] = processor;
    }

    // 触发事件:传入事件数据自动匹配处理器
    public int HandleEvent<TEventData>(TEventData eventData)
    {
        if (_processors.TryGetValue(typeof(TEventData), out var processor))
        {
            return ProcessorInvoker.InvokeGetNumber(processor, eventData);
        }
        throw new KeyNotFoundException($"未找到处理{typeof(TEventData).Name}类型事件的处理器");
    }
}

3. 游戏事件适配示例

定义游戏事件数据类与对应处理器:

// 回合事件数据
public class TurnEventData
{
    public int TurnNumber { get; set; }
}

// 卡牌打出事件数据
public class CardPlayedEventData
{
    public string CardId { get; set; }
    public string PlayerId { get; set; }
}

// 回合事件处理器
public class TurnProcessor : IGenericBase<TurnEventData>
{
    public int GetNumber(TurnEventData args)
    {
        // 根据回合数生成命令标识
        return args.TurnNumber * 100;
    }
}

// 卡牌事件处理器
public class CardPlayedProcessor : IGenericBase<CardPlayedEventData>
{
    public int GetNumber(CardPlayedEventData args)
    {
        // 根据卡牌信息生成命令标识
        return args.CardId.GetHashCode();
    }
}

使用方式:

// 注册所有处理器
var container = new ProcessorContainer();
container.Register(new TurnProcessor());
container.Register(new CardPlayedProcessor());

// 触发回合事件
var turnEvent = new TurnEventData { TurnNumber = 5 };
int turnCommand = container.HandleEvent(turnEvent);

// 触发卡牌打出事件
var cardEvent = new CardPlayedEventData { CardId = "Fireball", PlayerId = "Player1" };
int cardCommand = container.HandleEvent(cardEvent);

内容的提问来源于stack exchange,提问作者nuclear sweet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 18:46:09