接受mutliple但指定枚举的函数

时间:2016-02-18 13:33:38

标签: c# enums type-safety

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

我所拥有的是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,但我不知道如何做到这一点。< / p>

编辑:情况

很多好的建议,但没有任何东西包含我想要的一切。我在这里添加了我目前的代码,希望能够更好地解决这个问题。

我正在制作扫雷克隆试用Unity 2D。我已根据与thor::ActionMap一起使用的C ++库中的SFML类创建了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接受两个不同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

5 个答案:

答案 0 :(得分:1)

我认为重载是最好的选择。将因子do lots of stuffdo lots of other stuff纳入自己的方法中,你就不会有任何噩梦。

如果这真的不可能,那么你所拥有的就是好的。只需根据需要将e投射到E1E2。这有点粗略,但它是一种私人方法,所以丑陋不应该传播得太远。

答案 1 :(得分:1)

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

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

确保没有人看到它。

答案 2 :(得分:1)

你不想改变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
}

答案 3 :(得分: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

执行此操作的另一种方法(在更改更多代码时)将反转依赖关系:根据传入的ActionEActionType提供设置自己的EControls的方法。

答案 4 :(得分:1)

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