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