C#-以类型安全的方式将输入对象映射到影响对象
我试图解决的问题在概念上非常简单。我将从游戏的角度来解释,但我相信这个概念适用于任何具有输入、效果关系的对象: 能力由输入对象列表和效果对象列表组成。输入对象定义用户交互的类型(如选择地面瓷砖或选择单元),每个输入对象将从用户处获得特定类型的目标(例如瓷砖或单元)。效果对象定义对特定类型目标的效果(例如移动到地面瓷砖或损坏单元) 在代码中使用能力包括:C#-以类型安全的方式将输入对象映射到影响对象,c#,generics,casting,covariance,type-safety,C#,Generics,Casting,Covariance,Type Safety,我试图解决的问题在概念上非常简单。我将从游戏的角度来解释,但我相信这个概念适用于任何具有输入、效果关系的对象: 能力由输入对象列表和效果对象列表组成。输入对象定义用户交互的类型(如选择地面瓷砖或选择单元),每个输入对象将从用户处获得特定类型的目标(例如瓷砖或单元)。效果对象定义对特定类型目标的效果(例如移动到地面瓷砖或损坏单元) 在代码中使用能力包括: 按顺序提示用户输入对象(等待用户完成每个对象的输入,然后再转到下一个) 按顺序执行效果(每个效果将从其映射到的输入对象获取其数据) 因此,定
- 按顺序提示用户输入对象(等待用户完成每个对象的输入,然后再转到下一个)
- 按顺序执行效果(每个效果将从其映射到的输入对象获取其数据)
Ability: {
Inputs: {
Select Tile,
Select Unit
}
Effects: {
Move to (input 1),
Deal 10 damage (input 2)
}
}
理想情况下,我希望使从输入到效果的映射类型安全,因为每种类型的效果都有它期望的目标数据类型(例如,tile或unit)。
下面是我编写的代码示例,用于表示代码中的动作、输入和效果对象:
public class AbilityData
{
List<InputData<Target>> inputs;
List<AbilityEffect> effects;
public void exampleCreate()
{
BaseInputData<Tile> input = new TileSelectData();
inputs.Add(input);
effect = new List<AbilityEffect>();
BaseAbilityEffect<Tile> effect = new BaseAbilityEffect<Tile>(new TileEffect(), input);
effects.Add(effect);
}
public void exampleRun()
{
foreach (InputData<AbilityInput> input in inputs)
{
input.promptInput();
}
foreach (AbilityEffect effect in effects)
{
effect.execute();
}
}
}
public interface AbilityEffect
{
void execute();
}
public class BaseAbilityEffect<T> : AbilityEffect where T : Target
{
InputData<T> input;
TargetEffect<T> effect;
public BaseAbilityEffect(TargetEffect<T> tEffect, InputData<T> input) {
this.input = input;
this.effect = tEffect;
}
public void execute()
{
effect.execute(input.getInput());
}
}
public class TargetEffect<T> where T : Target
{
public virtual void execute(T input) { }
}
public class UnitEffect : TargetEffect<Unit>
{
public override void execute(Unit unit)
{
// Do something on the unit
}
}
public class TileEffect : TargetEffect<Tile>
{
public override void execute(Tile tile)
{
// Do something on the tile
}
}
public interface InputData<out T>
{
void promptInput();
T getInput();
}
public abstract class BaseInputData<T> : InputData<T> where T : Target
{
public abstract T getInput();
public abstract void promptInput();
}
public class TileSelectData : BaseInputData<Tile>
{
public override Tile getInput()
{
// Return the stored input
}
public override void promptInput()
{
// prompt for the input and store it.
}
}
public class UnitSelectData : BaseInputData<Unit>
{
public override Unit getInput()
{
// Return the stored input
}
public override void promptInput()
{
// prompt for the input and store it.
}
}
对于InputData接口,但因为它使用协方差,所以我不能将泛型类型参数用作任何接口函数的参数
有办法绕过这个限制吗?这个概念似乎非常简单:确保效果对象仅与相同目标类型的输入对象匹配。当然,我可以用蛮力通过一堆不安全的强制转换来实现这一点,但似乎应该有更好的方法。我通过添加另一个类型Tinput来编译代码。我不知道这是否是你想要的:
public class AbilityData
{
List<InputData<Target,Target>> inputs;
List<AbilityEffect> effects;
public void exampleCreate()
{
BaseInputData<Tile,Target> input = new TileSelectData();
inputs.Add(input);
var effects = new List<AbilityEffect>();
var effect = new BaseAbilityEffect<Tile,Target>(new TileEffect(), input);
effects.Add(effect);
}
public void exampleRun()
{
foreach (InputData<AbilityInput, AbilityInput> input in inputs)
{
input.promptInput();
}
foreach (AbilityEffect effect in effects)
{
effect.execute();
}
}
}
public interface AbilityEffect
{
void execute();
}
public class BaseAbilityEffect<T,Tinput> : AbilityEffect
where T : Target,Tinput
{
InputData<T,Tinput> input;
TargetEffect<T> effect;
public BaseAbilityEffect(TargetEffect<T> tEffect, InputData<T,Tinput> input)
{
this.input = input;
this.effect = tEffect;
}
public void execute()
{
effect.execute(input.getInput());
}
}
public class TargetEffect<T> where T : Target
{
public virtual void execute(T input) { }
}
public class UnitEffect : TargetEffect<Unit>
{
public override void execute(Unit unit)
{
// Do something on the unit
}
}
public class TileEffect : TargetEffect<Tile>
{
public override void execute(Tile tile)
{
// Do something on the tile
}
}
public interface InputData<out T,Tinput>
where T: Tinput
{
void promptInput();
T getInput();
void overrideInput(Tinput input);
}
public class Target
{
}
public class Tile : Target
{
}
public class TileSelectData : BaseInputData<Tile,Target>
{
public TileSelectData()
{
}
}
public class Unit : Target
{
}
public class AbilityInput
{
}
public class BaseInputData<T,Tinput> : InputData<T,Tinput>
where T:Tinput
{
public T getInput()
{
throw new NotImplementedException();
}
public void overrideInput(Tinput input)
{
throw new NotImplementedException();
}
public void promptInput()
{
throw new NotImplementedException();
}
}
公共类能力数据
{
列出投入;
列表效应;
public void示例create()
{
BaseInputData输入=新的TileSelectData();
输入。添加(输入);
var effects=新列表();
var效应=新的BaseAbilityEffect(新的TileEffect(),输入);
效果。添加(效果);
}
public void示例运行()
{
foreach(输入中的输入数据输入)
{
input.prompInput();
}
foreach(能力效果中的效果)
{
effect.execute();
}
}
}
公共接口能力效果
{
void execute();
}
公共类BaseAbilityEffect:AbilityEffect
式中T:目标,锡箔
{
输入数据输入;
目标效应;
公共基础能力影响(目标影响、输入数据输入)
{
这个输入=输入;
这个效应=特效;
}
public void execute()
{
effect.execute(input.getInput());
}
}
公共类TargetEffect,其中T:Target
{
公共虚拟空执行(T输入){}
}
公共类UnitEffect:TargetEffect
{
公共覆盖无效执行(单位)
{
//在装置上做点什么
}
}
公共类TileEffect:TargetEffect
{
公共覆盖无效执行(平铺平铺)
{
//在瓷砖上做点什么
}
}
公共接口输入数据
T:Tinput在哪里
{
无效提示输入();
T getInput();
无效覆盖输入(Tinput输入);
}
公共类目标
{
}
公共类磁贴:目标
{
}
公共类TileSelectData:BaseInputData
{
公共TileSelectData()
{
}
}
公共课单元:目标
{
}
公共类能力输入
{
}
公共类BaseInputData:InputData
T:Tinput在哪里
{
公共T getInput()
{
抛出新的NotImplementedException();
}
公共无效覆盖输入(Tinput输入)
{
抛出新的NotImplementedException();
}
公共无效提示输入()
{
抛出新的NotImplementedException();
}
}
很抱歉,代码已被修改,以使其更具一般性和可理解性(原始代码已编译)。更改的问题是,现在在InputData中,您必须定义两种类型,out T和Tinput,但实际上它们应该始终是相同的类型(InputData只处理一种类型的目标)。在您的示例中,第一个效果作用于第一个输入,第二个效果作用于第二个输入。总是这样吗?不一定。例如,你可以有一个只有一个输入的异能,但有多个效果(例如输入:选择一个单位,效果:1)对该单位造成伤害,2)击晕该单位)。每个效果只与一个输入关联(当前已传递到BaseAbilityEffect构造函数),并作用于该输入。我发现命名有点不直观,这使得很难概念化事物应该如何工作。当然,我没有完整的上下文,但我想我会将输入重命名为Subject或其他。让我感到奇怪的是,效果和输入存储在单独的列表中,为了让你的问题和需求更容易理解,你应该举例说明这段代码应该如何使用。Typesafety始终仅在“客户代码”方面具有含义。如果客户机代码将是完全多态的,那么typesafety将添加零实际安全性。
public class AbilityData
{
List<InputData<Target,Target>> inputs;
List<AbilityEffect> effects;
public void exampleCreate()
{
BaseInputData<Tile,Target> input = new TileSelectData();
inputs.Add(input);
var effects = new List<AbilityEffect>();
var effect = new BaseAbilityEffect<Tile,Target>(new TileEffect(), input);
effects.Add(effect);
}
public void exampleRun()
{
foreach (InputData<AbilityInput, AbilityInput> input in inputs)
{
input.promptInput();
}
foreach (AbilityEffect effect in effects)
{
effect.execute();
}
}
}
public interface AbilityEffect
{
void execute();
}
public class BaseAbilityEffect<T,Tinput> : AbilityEffect
where T : Target,Tinput
{
InputData<T,Tinput> input;
TargetEffect<T> effect;
public BaseAbilityEffect(TargetEffect<T> tEffect, InputData<T,Tinput> input)
{
this.input = input;
this.effect = tEffect;
}
public void execute()
{
effect.execute(input.getInput());
}
}
public class TargetEffect<T> where T : Target
{
public virtual void execute(T input) { }
}
public class UnitEffect : TargetEffect<Unit>
{
public override void execute(Unit unit)
{
// Do something on the unit
}
}
public class TileEffect : TargetEffect<Tile>
{
public override void execute(Tile tile)
{
// Do something on the tile
}
}
public interface InputData<out T,Tinput>
where T: Tinput
{
void promptInput();
T getInput();
void overrideInput(Tinput input);
}
public class Target
{
}
public class Tile : Target
{
}
public class TileSelectData : BaseInputData<Tile,Target>
{
public TileSelectData()
{
}
}
public class Unit : Target
{
}
public class AbilityInput
{
}
public class BaseInputData<T,Tinput> : InputData<T,Tinput>
where T:Tinput
{
public T getInput()
{
throw new NotImplementedException();
}
public void overrideInput(Tinput input)
{
throw new NotImplementedException();
}
public void promptInput()
{
throw new NotImplementedException();
}
}