如何将函数参数限制为某些特定枚举?最好在编译时检查它(虽然我怀疑这是可能的)。
我所拥有的是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
肯定是E1
或E2
,但我不知道如何做到这一点。< / 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.TilePressed
和EControls.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
答案 0 :(得分:1)
我认为重载是最好的选择。将因子do lots of stuff
和do lots of other stuff
纳入自己的方法中,你就不会有任何噩梦。
如果这真的不可能,那么你所拥有的就是好的。只需根据需要将e
投射到E1
或E2
。这有点粗略,但它是一种私人方法,所以丑陋不应该传播得太远。
答案 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
。
执行此操作的另一种方法(在更改更多代码时)将反转依赖关系:根据传入的Action
为EActionType
提供设置自己的EControls
的方法。
答案 4 :(得分:1)
您需要分两步创建Action
。 SetControl(...)
的调用者知道源是鼠标按钮还是键。因此调用者会创建操作对象,如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));