【问题标题】:How can I rewrite generic function in C# into OOP to avoid switching on types?如何将 C# 中的泛型函数重写为 OOP 以避免切换类型?
【发布时间】:2020-04-06 08:33:02
【问题描述】:

我有一个向服务器发送请求的客户端。请求和响应也是游戏事件,因此我可以将请求和响应发送到代码中需要它们的任何位置:

public class Message {}

public interface IOnlineRequest : IGameEvent {}

public interface IOnlineResponse : IGameEvent {}

public abstract class OnlineRequest<T> : Message, IOnlineRequest {
    public abstract T Request { get; set; }
}

public abstract class OnlineResponse<T> : Message, IOnlineResponse {
    public abstract T Response { get; set; }
}

public sealed class OPingRequest : OnlineRequest<PingRequest>
{
    public override PingRequest Request { get; set; }
}

public sealed class OPingResponse : OnlineResponse<PingResponse>
{
    public override PingResponse Response { get; set; }
}

有一个具有以下成员的管理器类:

private SortedDictionary<int, IOnlineRequest> requests_ = new SortedDictionary<int, IOnlineRequest>();
private SortedDictionary<int, IOnlineResponse> responses_ = new SortedDictionary<int, IOnlineResponse>();

我迭代请求并打开类型(这是我不喜欢的部分,想用 OOP 重写它):

bool error = false;
foreach (var request in requests_)
{
    if (!error)
    {
        switch (request.Value)
        {
            case OPingRequest req:
                error = await ProcessRequest<PingRequest, PingResponse, OPingResponse>(request.Key, req, Profile.PingAsync);
                break;
            case OKeepAliveRequest req:
                error = await ProcessRequest<KeepAliveRequest, KeepAliveResponse, OKeepAliveResponse>(request.Key, req, Profile.KeepAliveAsync);
                break;
            case OLoginRequest req:
                error = await ProcessRequest<LoginRequest, LoginResponse, OLoginResponse>(request.Key, req, Profile.LoginAsync);
                break;
            case OCounterRequest req:
                error = await ProcessRequest<CounterRequest, CounterResponse, OCounterResponse>(request.Key, req, Profile.CounterAsync);
                break;
            default:
                throw new NotImplementedException("Add code for your request type above!");
        }
    }
    else
    {
        break;  // Break out of foreach
    }
}

这是通用的 ProcessRequest 函数:

private async Task<bool> ProcessRequest<T, U, V>(int num, OnlineRequest<T> req, Func<T, CancellationToken, Task<U>> func)
    where T : class, IMessage<T>
    where U : class, IMessage<U>
    where V : OnlineResponse<U>, new()
{
    req.State = new RequestState();
    U resp = null;

    try
    {
        resp = await func(req.Request);

        req.State.Status = RequestStatus.Success;
        req.State.Message = "";
    }
    catch (Exception ex)
    {
        req.State.Status = RequestStatus.Failed;
        req.State.Message = ex.ToString();
    }

    responses_.Add(num, new V { State = req.State, Response = resp });

    return true;
}

发现很难在 OOP 中重新实现这一点,主要是因为像 PingRequest, PingResponse 这样的类型是具体类型并且它们是生成的,所以我别无选择,也无法更改它们。

我想要实现的是:

bool error = false;
foreach (var request in requests_)
{
    if (!error)
    {
        error = request.Process();
    }
    else
    {
        break;
    }
}

【问题讨论】:

  • 您的原始实现中有多少个 switch case?如果只有一个,应该没问题。如果有很多,请制作一个策略模式和一个Dictionary&lt;type,Func&gt; 来替换 switch case。
  • @LouisGo 我估计会有 100 多种请求类型

标签: c# oop generics


【解决方案1】:

主要是因为像PingRequest、PingResponse这样的类型是具体的类型,它们是生成的,所以我别无选择,也无法更改它们

大多数代码生成器工具都有通过partial class 声明类型的共同点。这意味着您可以创建一个单独的代码文件来添加东西无需编辑生成的文件,例如接口实现:

namespace TheSameNamespaceAsBefore {
    partial class PingRequest : IMyInterface {
        bool IMyInterface.DoTheThing() { /* your code here */ }
    }
    partial class PingResponse : IMyInterface {
        bool IMyInterface.DoTheThing() { /* your code here */ }
    }
}

现在您可以简单地执行以下操作:

if (request.Value is IMyInterface foo) { error = foo.DoTheThing(); }
else { /* warn? throw? set error to true? break? */ }

您也可以使用通用的基本类型,但这里的接口似乎很好。

(有时代码生成器还会生成“部分方法” - 您可以选择在自己的部分类文件中实现的存根,以提供在生成代码的特定点调用的功能;如果您不实现它们,调用只是在编译过程中蒸发,就好像它们从未存在过一样)

【讨论】:

  • 不幸的是,PingRequest 是这样标记的 public sealed class PingRequest : IMessage&lt;PingRequest&gt;, IMessage, IEquatable&lt;PingRequest&gt;, IDeepCloneable&lt;PingRequest&gt;
  • @CrHasher 这很烦人;是什么工具生成的?坦率地说,我会向他们记录一个错误:通常的做法是始终将生成的类型声明为 partial
  • 我使用的库是 Google Protobuf,但问题不在 Google 一方,因为 protoc 使用 public sealed partial class PingRequest : pb::IMessage&lt;PingRequest&gt; 生成类,但 Unity 将其从 dll 导入为 public sealed class PingRequest : IMessage&lt;PingRequest&gt;, IMessage, IEquatable&lt;PingRequest&gt;, IDeepCloneable&lt;PingRequest&gt;
  • @CrHasher 对;您需要将您的界面等放入您要导入的 dll 中partial 在编译时使用——一旦你编译了那个类型:就是这样。所以:只要把这段代码放在那里,在你生成的任何dll中
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-08-18
  • 1970-01-01
  • 1970-01-01
  • 2021-12-08
  • 2011-04-16
  • 2021-10-13
  • 2016-09-14
相关资源
最近更新 更多