简体   繁体   English

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

[英]Cast to object with generic type when type is unkown

I have a set of Option objects for a settings screen for my game. 我为游戏的设置屏幕设置了一组Option对象。 The Option class has a generic type that is different for each of the child classes. Option类的泛型类型对于每个子类均不同。

The classes currently look like the following: 当前的类如下所示:

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

I constructed the objects like this: 我构造了这样的对象:

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

And put them in a list: 并将它们放在列表中:

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

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

The problem arises when I, for example, want to iterate through all options and change their value to the last one in their range, or do any sort of value change. 例如,当我想遍历所有选项并将其值更改为它们范围内的最后一个选项或进行任何类型的值更改时,就会出现问题。 In java I would do the following: 在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]);
        }
    }

How would I accomplish a similar thing in C#? 我将如何在C#中完成类似的工作?

Here's the "ugly base class" version and a couple of C#-ifying changes as well: 这是“丑陋的基类”版本,以及一些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();
}

When you want/need to work without having a generic parameter, you use the Option type and its ObjXxx properties to access its values. 当您希望/不需要通用参数就可以工作时,可以使用Option类型及其ObjXxx属性来访问其值。 However, when you do have the generic type parameter available, Option<T> is still the preferred means of accessing this data. 但是,当确实有泛型类型参数可用时, Option<T>仍然是访问此数据的首选方法。

I'm still slightly dubious of Name being mutable... 我仍然对Name的可变性有些怀疑...

While avoiding commenting on the class design chosen, you could add a method to IOption which does exactly what you want. 在避免对所选的类设计发表评论的同时,您可以向IOption添加一个方法,该方法完全可以实现您想要的功能。 This is, in fact, the purpose of interfaces after all - to hide such details from consumers. 实际上,这毕竟是接口的目的-向消费者隐藏这样的细节。 If all you want to do is update the value to the last value in the range, then doing something like the following should support that: 如果您要做的只是将值更新为该范围内的最后一个值,则执行以下操作应支持:

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

It seems that the function itself doesn't require input from the call site. 似乎该函数本身不需要调用站点的输入。 Perhaps it could have a much better name to encompass the exit behavior as well. 也许它也可以有一个更好的名称来包含退出行为。

EDIT: I think it's important to emphasize that this behavior should not be decided from the call site. 编辑:我认为重要的是要强调这种行为不应由呼叫站点决定。 The data is held within the IOption implementation. 数据保存在IOption实现中。 You're taking it out of IOption just to pass it right back into IOption -- why even expose the data at all? 您将其从IOption只是为了将其直接传递回IOption -为什么还要公开数据呢? In general I find it reduces coupling to limit the data exposed and instead expose the behaviors. 通常,我发现它减少了耦合,从而限制了公开的数据,而是公开了行为。 You already have the interface set up for this, all you're missing is a new method on IOption that does what you need it to do - and then each implementation can do it their own special way (which it seems is the same for every implementation exception exit). 您已经为此设置了接口,所缺少的只是IOption上的新方法,它可以完成您需要做的事情-然后每个实现都可以用自己的特殊方式来实现(每个方法似乎都一样)实现异常退出)。

Create additional interface that has object instances of value/values 创建具有值object实例的其他接口

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

Then your options implement this interface: 然后,您的选项将实现此接口:

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

Ofcourse this will not let you change Values array, but you can use them and call SetValue/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]);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM