接受多个但指定枚举的函数

Function that accepts mutliple, but specified enums

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

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

我目前拥有的简化版本(在 class 内)

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
}

为了完整起见:


我想我需要能够告诉编译器泛型 T 肯定是 E1E2,但我不知道该怎么做.



编辑:情况

很多好的建议,但没有一个能满足我的所有要求。我将在此处添加我目前必须的代码,希望能进一步阐明该问题。

我正在制作一个扫雷克隆来试用 Unity 2D。我在 thor::ActionMap class from the library Thor in C++ used with SFML 的基础上创建了一个 Action class。它只允许简洁的代码,例如(在 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 个不同的 enum。因此相当于示例代码中的 DoSomething(e)

我现在正在创建一种可以一致地更改控件的方法。它使用 enum EControls 作为键而不是 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

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

如果那真的不可能,那你有什么也好。只需将 e 转换为 E1E2 即可。这有点恶心,但它是一个私有方法,所以丑陋不应该传播得太远。

一个丑陋的方法:

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();
}

确保没有人看到它。

您不想更改 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
}

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

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 的方法].

您需要分两步创建 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));