接受多个但指定枚举的函数
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
}
为了完整起见:
DoSomething
行为完全不同,具体取决于给定的类型
DoSomething
在函数中被多次调用
- 我无法更改枚举
- 我不想改变
DoSomething
我想我需要能够告诉编译器泛型 T
肯定是 E1
或 E2
,但我不知道该怎么做.
编辑:情况
很多好的建议,但没有一个能满足我的所有要求。我将在此处添加我目前必须的代码,希望能进一步阐明该问题。
我正在制作一个扫雷克隆来试用 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.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
我认为重载是最好的选择。将 do lots of stuff
和 do lots of other stuff
分解为它们自己的方法,您就不会做噩梦了。
如果那真的不可能,那你有什么也好。只需将 e
转换为 E1
或 E2
即可。这有点恶心,但它是一个私有方法,所以丑陋不应该传播得太远。
一个丑陋的方法:
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
的方法].
您需要分两步创建 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));
如何将函数参数限制为某些特定的枚举?最好在编译时检查它(尽管我怀疑这是可能的)。
我有 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
}
为了完整起见:
DoSomething
行为完全不同,具体取决于给定的类型DoSomething
在函数中被多次调用- 我无法更改枚举
- 我不想改变
DoSomething
我想我需要能够告诉编译器泛型 T
肯定是 E1
或 E2
,但我不知道该怎么做.
编辑:情况
很多好的建议,但没有一个能满足我的所有要求。我将在此处添加我目前必须的代码,希望能进一步阐明该问题。
我正在制作一个扫雷克隆来试用 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.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
我认为重载是最好的选择。将 do lots of stuff
和 do lots of other stuff
分解为它们自己的方法,您就不会做噩梦了。
如果那真的不可能,那你有什么也好。只需将 e
转换为 E1
或 E2
即可。这有点恶心,但它是一个私有方法,所以丑陋不应该传播得太远。
一个丑陋的方法:
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
的方法].
您需要分两步创建 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));