【问题标题】:Function that accepts mutliple, but specified enums接受多个但特定枚举的函数
【发布时间】:2016-02-18 13:33:37
【问题描述】:

如何将函数参数限制为某些特定的枚举?最好在编译时检查它(尽管我怀疑这是可能的)。

我有 2 个枚举(KeyCode 用于键盘键,Mouse.Button 用于鼠标按钮),它们在代码中的处理方式完全相同。我可以简单地重载函数并复制粘贴内容,但是,我想避免噩梦。

我目前拥有的简化版(在课堂内)

enum E1 { Zero, One, Two }
enum E2 { Three, Four, Five }

// Overloads so users can only use this with enums only of type E1 or E2
public void DoEnumStuff(E1 e) {
    DoEnumStuffTemplate(e);
}
public void DoEnumStuff(E2 e) {
    DoEnumStuffTemplate(e);
}

// private function so users cannot access this generic one
private void DoEnumStuffTemplate<T>(T e) where T : struct, IConvertible {
    // check type for safety
    if (!typeof(T).IsEnum || typeof(T).Name != "E1" || typeof(T).Name != "E2")
        throw new ArgumentException();

    // do lots of stuff
    DoSomething(e); //<- overloaded function, accepts only E1 and E2 =ERROR
    // do lots of other stuff
}

为了完整起见:

  • DoSomething 的行为完全不同,具体取决于给定的类型
  • DoSomething在函数中被多次调用
  • 我无法更改枚举
  • 我不想改变DoSomething

我想我需要能够告诉编译器通用的T 肯定是E1E2,但我不知道该怎么做。



编辑:情况

很多好的建议,但没有包含我想要的一切。我将在这里添加我目前需要的代码,希望能更清楚地解决这个问题。

我正在制作一个扫雷克隆来试用 Unity 2D。我基于与SFML 一起使用的C++ 库Thor 中的thor::ActionMap 类创建了一个Action 类。它只是允许简洁的代码,例如(在 C++ 中)

ActionMap actionMap<string>;
actionMap["Fire"] = Action(Keyboard::LeftControl) || Action(Mouse::Left);
// stuff
while (game.IsRunning()) {
    if (actionMap["Fire"].IsActive()) //true if left control or left mouse button is held
        // FIRE
    // probably more stuff
}

其中ActionMap 只是一个键(这里是string)和Action 的字典。如您所见,Action 接受键盘和鼠标按钮,它们是 2 个不同的enums。因此相当于示例代码中的DoSomething(e)

我现在正在创建一种可以一致地更改控件的方法。它使用enumEControls 作为键而不是string。这里KeyCode 包含所有键盘键和Mouse.Button 所有鼠标按钮。我需要在这里区分按下和释放按钮,这就是为什么EControls.TilePressedEControls.TileReleased 将具有相同的键并且需要区别对待例如EControls.GameEscape。这段代码又是在 C# 中。

private ActionMap _controls = new ActionMap<EControls>();

// Set controls for a keyboard key
public void SetControl(EControls control, KeyCode key) {
    switch (control) {
        // If either TilePressed or Released was given, set them both to the same key
        case EControls.TilePressed:
        case EControls.TileReleased:
            //Here Action(...) is DoSomething(...) from the example code
            _controls[EControls.TilePressed] = new Action(key, Action.EActionType.PressOnce);
            _controls[EControls.TileReleased] = new Action(key, Action.EActionType.ReleaseOnce);
            break;
        case EControls.TileFlagPressed:
        case EControls.TileFlagReleased:
            _controls[EControls.TileFlagPressed] = new Action(key, Action.EActionType.PressOnce);
            _controls[EControls.TileFlagReleased] = new Action(key, Action.EActionType.ReleaseOnce);
            break;
        case EControls.GameEscape:
            _controls[EControls.GameEscape] = new Action(key, Action.EActionType.ReleaseOnce);
            break;
        default:
            throw new ArgumentOutOfRangeException("control");
    }
}

// Set controls for a mouse button
public void SetControl(EControls control, Mouse.Button button) {
    // copy-pasted code :(
    case EControls.TilePressed:
        case EControls.TileReleased:
            _controls[EControls.TilePressed] = new Action(button, Action.EActionType.PressOnce);
            _controls[EControls.TileReleased] = new Action(button, Action.EActionType.ReleaseOnce);
            break;
        case EControls.TileFlagPressed:
        case EControls.TileFlagReleased:
            _controls[EControls.TileFlagPressed] = new Action(button, Action.EActionType.PressOnce);
            _controls[EControls.TileFlagReleased] = new Action(button, Action.EActionType.ReleaseOnce);
            break;
        case EControls.GameEscape:
            _controls[EControls.GameEscape] = new Action(button, Action.EActionType.ReleaseOnce);
            break;
        default:
            throw new ArgumentOutOfRangeException("control");
    }
}

如您所见,几乎每一行代码中都存在new Action(...),而if (typeof(T).GetType() == typeof(E1)) 之类的代码基本上与复制粘贴函数的内容相同。这是我想避免的事情(复制粘贴在编译时甚至会更安全)。但就目前而言,这似乎是不可能的。

由于在更大的游戏中您可能会定期添加一些新控件,这会很烦人。

对不起,文字墙:s

【问题讨论】:

  • 我认为重载没问题....这就是它专门设计的..为什么你认为它不好?
  • 如果DoSomething 对不同类型的行为不同,为什么要强制将事情变成一个方法?只需重载并使用两个单独的方法。
  • "I'd like to avoid the nightmares" - 噩梦,例如......什么,究竟是什么?方法重载是该语言的一个非常简单的部分。重复的关注点可以很容易地重构。
  • 您应该考虑将 != "E1" 替换为 typeof(E1) 等。另外我建议在接收 int 的私有方法中做所有常见的事情,并在 DoSomething 的每个重载中调用它(e ) 并完全删除 DoEnumStuffTemplate
  • 我不想要复制意大利面,因为该功能很长并且需要定期更新。这是我可以做噩梦的复制粘贴,而不是方法重载。

标签: c# enums type-safety


【解决方案1】:

这是一个类似于工厂模式的重构:

public void SetControl(EControls control, Func<Action.EActionType, Action> createAction)
{
    switch (control)
    {
        case EControls.TilePressed:
        case EControls.TileReleased:
            _controls[EControls.TilePressed] = createAction(Action.EActionType.PressOnce);
            _controls[EControls.TileReleased] = createAction(Action.EActionType.ReleaseOnce);
            break;

        case EControls.TileFlagPressed:
        case EControls.TileFlagReleased:
            _controls[EControls.TileFlagPressed] = createAction(Action.EActionType.PressOnce);
            _controls[EControls.TileFlagReleased] = createAction(Action.EActionType.ReleaseOnce);
            break;

        case EControls.GameEscape:
            _controls[EControls.GameEscape] = createAction(Action.EActionType.ReleaseOnce);
            break;

        default:
            throw new ArgumentOutOfRangeException("control");
    }
}

// Call it later with:
SetControl(control, type => new Action(key, type));
SetControl(control, type => new Action(mouseButton, type));

您向SetControl 提供相当于部分填充的构造函数createAction,它只需要EActionType 即可完全创建Action

另一种方法(同时更改更多代码)是反转依赖关系:为Action 提供一种基于传入的EControls 设置自己的EActionType 的方法。

【讨论】:

    【解决方案2】:

    我认为超载是您最好的选择。将do lots of stuffdo lots of other stuff 分解到它们自己的方法中,你就不会做任何噩梦了。

    如果这真的不可能,那么你所拥有的就很好。只需将e 转换为E1E2 即可。有点恶心,不过是私下的方法,所以丑不要传得太远。

    【讨论】:

    • 但是,do lots of stuff 实际上也包含很多DoSomethings。不过应该提到这一点。而且我无法转换为E1E2,因为我不知道它们在函数中是哪种类型。如果可以的话,应该没有任何问题:-)
    • @Didii 应该还有办法,甚至可以将Actions 传递到子方法中,或者将不使用DoSomething 的段更好地拆分。也许你应该写一次你的代码,然后把它放到 Code Review 上,看看人们对你的具体案例的看法?
    • @31eee384 我已经添加了我正在使用的代码。我希望这可以更清楚地说明我为什么不喜欢复制粘贴以及为什么转换为 E1E2 与复制粘贴基本相同。
    【解决方案3】:

    一个丑陋的,但一种方式:

    private void DoEnumStuffTemplate<T>(T e) where T : struct, IConvertible
    {
        if (typeof(T) == typeof(E1))
            DoEnumStuff((E1)(object)e);
        else if (typeof(T) == typeof(E2))
            DoEnumStuff((E2)(object)e);
        else
            throw new ArgumentException();
    }
    

    确保没有人看到它。

    【讨论】:

    • 这只是变相的方法重载:p我想我需要复制粘贴的方式:(
    • 不不,这是通用方法,看它的签名! ;) 我写它只是因为你的评论 “我不能转换为 E1E2 因为我不知道它们在函数中是哪种类型” (并且丑陋地检查问题)。
    【解决方案4】:

    您不想更改DoSomething,但将其包装好吗?

    private void myDoSomething(T e) where T : struct, IConvertible
    {
        if (typeof(T).GetType().Name == "E1")
            DoSomething((E1)(object)e);
        else if (typeof(T).GetType().Name == "E2")
            DoSomething((E2)(object)e);
        else
            throw new ArgumentException();
    }
    
    
    // private function so users cannot access this generic one
    private void DoEnumStuffTemplate<T>(T e) where T : struct, IConvertible {
        // check type for safety
        if (!typeof(T).IsEnum || typeof(T).GetType().Name != "E1" || typeof(T).GetType().Name != "E2")
            throw new ArgumentException();
    
        // do lots of stuff
        myDoSomething(e); //<- overloaded function, accepts only E1 and E2 =ERROR
        // do lots of other stuff
    }
    

    【讨论】:

      【解决方案5】:

      您需要分两步创建ActionSetControl(...) 的调用者知道源是鼠标按钮还是按键。所以调用者会创建像new Action(key)new Action(button) 这样的动作对象。
      此操作对象被传递给SetControl(control, Action action) 方法。
      SetControl 知道动作类型。它需要Action 中的一个方法,其中可以设置操作类型,例如Action.SetActionType(Action.EActionType actionType)

      所以SetControl 方法是:

      // Set controls for an action
      public void SetControl(EControls control, Action action) {
          switch (control) {
              // If either TilePressed or Released was given, set them both to the same key
              case EControls.TilePressed:
              case EControls.TileReleased:
                  //Here Action(...) is DoSomething(...) from the example code
                  _controls[EControls.TilePressed] = action.SetActionType(Action.EActionType.PressOnce);
                  _controls[EControls.TileReleased] = action.SetActionType(Action.EActionType.ReleaseOnce);
                  break;
              case EControls.TileFlagPressed:
              case EControls.TileFlagReleased:
                  _controls[EControls.TileFlagPressed] = action.SetActionType(Action.EActionType.PressOnce);
                  _controls[EControls.TileFlagReleased] = action.SetActionType(Action.EActionType.ReleaseOnce);
                  break;
              case EControls.GameEscape:
                  _controls[EControls.GameEscape] = action.SetActionType(Action.EActionType.ReleaseOnce);
                  break;
              default:
                  throw new ArgumentOutOfRangeException("control");
          }
      }
      

      这个方法是这样调用的:

      SetControl(control, new Action(key));
      SetControl(control, new Action(mouseButton));
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-27
        相关资源
        最近更新 更多