如何使用基类和派生类处理 C# 中的多态事件类型?

问题描述 投票:0回答:1

我正在尝试在 C# 中实现多态性,以便使用单个

ProcessEvent
方法处理各种事件类型。这是我当前设置的示例:

public class BaseProposedEvent
{
    public required string EventType { get; set; }
}

public class ProposeTeamCreatedEvent : BaseProposedEvent
{
    public required string TeamName { get; set; }
}

我想将派生类的实例(如

ProposeTeamCreatedEvent
)传递给
ProcessEvent
,同时将参数声明为
BaseProposedEvent
。但是,由于
TeamName
不是基类的属性,因此我无法在方法中直接访问它。例如:

public void ProcessEvent(BaseProposedEvent proposedEvent)
{
    // I can't access `TeamName` if the event is not a `ProposeTeamCreatedEvent`.
}

我想到的一个解决方法是将 JSON 字符串和事件类型传递给

ProcessEvent
,但这种方法会变得混乱,因为它需要在服务器和客户端上进行序列化/反序列化。

在 TypeScript 中,这个问题可以通过类型联合和可区分联合轻松解决:

export type IncomingMessage =
  | IAuthMessage
  | IProposeEventMessage
  | IProposeEndGameMessage
  | IHeartBeatMessage;

export interface IAuthMessage {
  type: "auth";
  gameId: string;
  userToken: string;
}

如何在 C# 中实现类似级别的灵活性,而无需求助于手动 JSON 处理?是否有更好的方法以干净且类型安全的方式处理多态事件类型?

另请注意,这是使用 SignalR 的应用程序的一部分。中心看起来像这样:

public async Task ProposeEvent(int gameId, string userToken, string eventType, BaseProposedEvent proposedEvent)
        {
            // todo: eventId should be set on client, no?
            var gameState = gameStateService.GetGameState(gameId);
            if (gameState is null)
            {
                logger.LogWarning("User {userToken} tried to join game {gameId}. The game does not exist", userToken, gameId);
                throw new HubException("Game does not exist.");
            }

            if (!gameState.IsUserInGame(userToken))
            {
                logger.LogWarning("User {userToken} tried invoking JoinGame before joining via API", userToken);
                throw new HubException("You must join the game via the API before connecting.");
            }

            if (!gameState.IsGameAdmin(userToken))
            {
                logger.LogWarning("User {userToken} is not the admin of game {gameId}. The user may not propose an event.", userToken, gameId);
                throw new HubException("Only the admin may perform actions in this game.");
            }

            var processEventResult = await eventProcessor.ProcessEventAsync(proposedEvent, eventType, gameId, userToken, gameState);
            if (processEventResult.IsFailure)
            {
                logger.LogCritical("The proposed event was invalid. Event type: {eventType}", eventType);
                throw new HubException($"The proposed event was invalid. Event type: {eventType}.");
            }

            gameStateService.UpdateGameState(gameId, processEventResult.Value.UpdatedGameState);
            await Clients.Group(GameUtility.GetGameIdString(gameId)).SendGameEvent(processEventResult.Value);
            logger.LogInformation(
                "State updated for game {gameId}. The event was processed successfully. Event type: {eventType}",
                gameId,
                eventType
            );
        }
    ```

I added `eventType` as an argument to `ProcessEvent` to help when narrowing the type of the `BaseEvent`.
c# .net polymorphism signalr
1个回答
0
投票

假设添加

BaseProposedEvent.Process
方法来进行任何处理是不可行的,最简单的方法就是使用一个开关:

public void ProcessEvent(BaseProposedEvent proposedEvent)
{
    switch (proposedEvent)
    {
        case ProposeTeamCreatedEvent proposeTeamCreatedEvent: 
            var teamName = proposeTeamCreatedEvent.TeamName;
            break;
        default: throw new ArgumentOutOfRangeException(nameof(proposedEvent));
    }
}

此选项的缺点是您需要记住在添加新类型时添加新案例。一种替代方法是使用 访问者模式:

public interface IEventVisitor
{
    public void Visit(ProposeTeamCreatedEvent obj);
}
public abstract class BaseProposedEvent
{
    public string EventType { get; set; }
    public abstract void Accept(IEventVisitor visitor);
}

public class ProposeTeamCreatedEvent : BaseProposedEvent
{
    public string TeamName { get; set; }
    public override void Accept(IEventVisitor visitor) => visitor.Visit(this);
}

public class ProcessVisitor : IEventVisitor
{
    public void Visit(ProposeTeamCreatedEvent obj)
    {
        var teamName = obj.TeamName;

    }
}

这或多或少做了相同的事情,但是每当您添加新的事件类型时,编译器都会强制您向

IEventVisitor
接口添加新方法,并更新其所有实现。因此忘记某事的风险要小得多。您可以考虑让访问者返回通用值,即
public T Visit(ProposeTeamCreatedEvent obj)
,以防访问者需要返回值。

您还可以在 c# 中实现可区分联合,例如,请参阅这篇关于 either monad 的文章。但如果您使用多态性并且需要处理任意数量的类型,我不确定这是否是正确的方法。

© www.soinside.com 2019 - 2024. All rights reserved.