类型未知时强制转换为具有通用类型的对象

时间:2019-01-04 11:27:45

标签: c#

我为游戏的设置屏幕设置了一组Option对象。 Option类的泛型类型对于每个子类都不同。

当前的类如下:

    public interface IOption
    {
        string GetName();

        void SetName(string name);
    }
    //Note that this does not inherit from the Option class
    public class ExitOption : IOption
    {
        private string name;

        public ExitOption(string name) => this.name = name;

        public string GetName()
        {
            return name;
        }

        public void SetName(string name)
        {
            this.name = name;
        }
    }
    public class Option<T> : IOption
    {
        public string Name;
        private T value;
        private T defaultValue;
        public T[] Values;

        public Option(string name, T defaultValue, params T[] values)
        {
            (Name, value, this.defaultValue, Values) = (name, defaultValue, defaultValue, values);
        }

        public T GetValue()
        {
            return value;
        }

        public void SetValue(T value)
        {
            this.value = value;
        }

        public string GetName()
        {
            return Name;
        }

        public void SetName(string name)
        {
            this.Name = name;
        }
    }
    public class IntegerOption : Option<int>
    {
        private int minValue;
        private int maxValue;

        //                                                                                                         Creates an array of numbers between the min and max value
        public IntegerOption(string name, int defaultValue, int minValue, int maxValue) : base(name, defaultValue, RangeCreator.IntegerRange(minValue, maxValue))
        { }
    }
    //"Key" is an enum
    public class KeyOption : Option<Key>
    {
        //                                                                         Creates an array containing all enum constants
        public KeyOption(string name, Key defaultValue) : base(name, defaultValue, RangeCreator.EnumRange<Key>())
        {}
    }
}

我这样构造对象:

ExitOption exit = new ExitOption("Exit");
IntegerOption volume = new IntegerOption("Volume", 100, 0, 100);
KeyOption jump = new KeyOption("Jump", Key.Spacebar);

并将它们放在列表中:

List<IOption> options = new List<IOption>();

options.Add(exit);
options.Add(volume);
options.Add(jump);

例如,当我想遍历所有选项并将其值更改为它们范围内的最后一个值,或进行任何类型的值更改时,就会出现问题。在Java中,我将执行以下操作:

    for(IOption option : options)
    {
        if(option instanceof ExitOption)
        {
            //Handle exiting the menu
        }
        else
        {
            //Type is unkown, therefore I do not provide any type arguments
            Option currentOption = (Option) option;
            currentOption.SetValue(currentOption.Values[currentOption.Values.length - 1]);
        }
    }

我将如何在C#中完成类似的事情?

3 个答案:

答案 0 :(得分:2)

这是“丑陋的基类”版本,以及一些C#更改:

using System;
using System.Collections.Generic;
using System.Linq;

public interface IOption
{
    string Name { get; set; }
}
public class ExitOption : IOption
{
    public ExitOption(string name) => Name = name;
    public string Name { get; set; }
}
public abstract class Option : IOption
{
    public Option(string name) => Name = name;
    public string Name { get; set; }
    public abstract object ObjValue { get; set; }
  public abstract IEnumerable<object> ObjValues { get;}
}
public class Option<T> : Option
{
    private T _value;
    private List<T> _values;

    public Option(string name, T initialValue, params T[] values) : base(name)
    {
        (_value, _values) = (initialValue, values.ToList());
    }
    public override IEnumerable<object> ObjValues { get => _values.Cast<object>().AsEnumerable(); }
    public T Value { get => _value; set => _value = value; }
    public override object ObjValue { get => _value; set => _value = (T)value; }
    public IEnumerable<T> Values => _values.AsEnumerable();
}

当您希望/不需要通用参数就可以工作时,可以使用Option类型及其ObjXxx属性来访问其值。但是,当确实有通用类型参数可用时,Option<T>仍然是访问此数据的首选方法。

我仍然有点怀疑Name易变...

答案 1 :(得分:0)

在避免评论所选的类设计的同时,您可以向IOption添加一个方法,该方法完全可以实现您想要的功能。实际上,这毕竟是接口的目的-向消费者隐藏这样的细节。如果您要做的就是将值更新为该范围内的最后一个值,则执行以下操作应支持该操作:

public interface IOption
{
    string GetName();
    void SetName(string name);
    // or some other appropriate name for this
    void SetValueToLastInRange();
}

似乎函数本身不需要调用站点的输入。也许它也可以有一个更好的名称来包含退出行为。

编辑:我认为重要的是要强调这种行为不应由呼叫站点决定。数据保存在IOption实现中。您要从IOption中取出它只是将其直接传递回IOption中-为什么还要公开数据呢?通常,我发现它减少了耦合,从而限制了公开的数据,而是公开了行为。您已经为此设置了接口,您所缺少的只是IOption上的新方法,它可以完成您需要做的事情-然后每个实现都可以以自己的特殊方式来做(看来是对于每个实现异常出口都相同)。

答案 2 :(得分:0)

创建具有object个值实例的其他接口

public interface IValueOption : IOption
{
    void SetValue(object value);
    object GetValue();
    object[] Values { get; }
}

然后您的选项将实现此接口:

public class Option<T> : IOption, IValueOption
{
    object[] IValueOption.Values => Values.Cast<object>().ToArray();
    void IValueOption.SetValue(object value)
    {
        SetValue((T)value);
    }
    object IValueOption.GetValue()
    {
        return GetValue();
    }
   ...
}

当然,这不能让您更改Values数组,但可以使用它们并调用SetValue / GetValue。

foreach(IOption option in options)
{
    if(option is ExitOption)
    {
        //Handle exiting the menu
    }
    else if(option is IValueOption)
    {
        //Type is unkown, therefore I do not provide any type arguments
        IValueOption currentOption = (IValueOption)option;
        currentOption.SetValue(currentOption.Values[currentOption.Values.Length - 1]);
    }
}