Список C#, который принимает 2 или более типов перечисления

#c# #types

Вопрос:

У меня есть несколько перечислений, используемых в качестве идентификаторов, и я хочу использовать их в одном списке.

 public enum eUnit
{
    Villager,
    Warrior,
    Wizard,
}
public enum eVehicle
{
    Car,
    Train,
    Helicopter,
}
public enum eItem
{
    Apple,
    Steak,
    Pizza,
}
 

Возможно ли что-то подобное приведенному ниже коду?

 List<?enum?> myList = new List<?enum?>();
myList.Add(eUnit.Warrior);
myList.Add(eItem.Pizza);

if(myList[0].GetType() == typeof(eUnit))
    DoStuff();
...
...
 

Комментарии:

1. Почему ты хочешь это сделать? Строго типизированные коллекции создаются, чтобы помешать вам добавлять элементы разных типов. Даже если вы рассматриваете все перечисления как целые числа, лежащие в их основе, как бы вы определили, из какого перечисления они произошли?

2. Подумайте о том, чтобы отказаться от перечисления и вместо этого использовать полиморфизм классов и интерфейсы.

3. Перечисления на самом деле являются целыми числами. Поэтому вместо этого вам может понадобиться кортеж с типом и значением. List<(Type type, int val)> list = new List<(Type, int)>();

Ответ №1:

Другой способ-использовать дискриминируемый союз через библиотеку oneOf.

 var myList = new List<OneOf<eUnit, eVehicle, eItem>>()
{
    eUnit.Warrior, eVehicle.Car
};
myList[0].Switch(
    unit => Console.WriteLine("Unit"),
    vehicle => Console.WriteLine("Vehicle"),
    item => Console.WriteLine("Item")
);
 

Я сделал образец


Комментарии:

1. Будет забавно распаковать это…

2. @djv: Экземпляр oneOf хранит не только само значение, но и то, какой из типов он содержит (т. Е. Индекс от 0 до 2 в случае трех возможных типов). Таким образом, информация о типе перечисления не теряется во время бокса/распаковки.

3. @Heinzi круто, но мой комментарий был к предыдущей правке с List<object> myList = new List<object>()

4. @djv: А, ладно, имеет смысл. 🙂

Ответ №2:

Вы могли бы попробовать использовать динамический тип. Возможно, это было бы излишне, но это зависит от вас.

Ответ №3:

Вы можете реализовать свой собственный List<T> . Вот простой пример :

 public class EnumList
{
    private readonly List<Entry> _entriesList = new List<Entry>();
    public object this[int index]
    {
        get
        {
            var item = _entriesList[index];
            return Enum.Parse(item.Key, item.Value);
        }
        set
        {
            _entriesList[index] = new Entry(value.GetType(), value.ToString());
        }
    }

    private class Entry
    {
        public Type Key { get; set; }
        public string Value { get; set; }
        public Entry(Type key, string value)
        {
            Key = key;
            Value = value;
        }
    }

    public void Add<TEnum>(TEnum item) where TEnum : struct, Enum
    {
        _entriesList.Add(new Entry(typeof(TEnum), item.ToString()));
    }

    public List<TEnum> Get<TEnum>() where TEnum : struct, Enum
    {
        return _entriesList.Where(x => x.Key == typeof(TEnum)).Select(x => Enum.TryParse(x.Value, out TEnum result) ? result : default).ToList();
    }

    public bool Exists<TEnum>(TEnum item) where TEnum : struct, Enum
    {
        return _entriesList.Any(x => x.Key == typeof(TEnum) amp;amp; x.Value == item.ToString());
    }
}
 

Использование :

 var list = new EnumList();

list.Add(eUnit.Warrior);
list.Add(eItem.Pizza);
list.Add(eVehicle.Car);
list.Add(eVehicle.Helicopter);
list.Add(eUnit.Villager);
list.Add(eItem.Apple);

if((eUnit)list[0] ==  eUnit.Warrior || list.Exists(eUnit.Villager))
{
    // do stuff
}
 

Это всего лишь пример, который поможет вам реализовать свой собственный, помните, что вы всегда должны стараться сузить допустимые параметры, указав предполагаемые enum параметры, хотя вы можете использовать полный универсальный.

Ответ №4:

Как уже упоминал Андрей, использование дискриминируемого союза , такого как OneOf , или Either , может быть полезно в этом случае.

Если вы просто используете перечисления в качестве идентификатора, вы также можете создавать различные классы и использовать систему типов для сопоставления шаблонов. Это просто другой шаблон, который вы могли бы изучить, если вам нравится сопоставление шаблонов с конкретными элементами.

 internal abstract class EnumType
{
    protected EnumType(int value, string name)
    {
        if(value < 0) { throw new InvalidOperationException("Value cannot be less than 0."); }
        this._value = value;
        this._name = name ?? throw new ArgumentNullException(nameof(name));
    }

    public static implicit operator int(EnumType x)
        => x?._value ?? throw new ArgumentNullException(nameof(x));

    public static implicit operator string(EnumType x)
        => x?._name ?? throw new ArgumentNullException(nameof(x));

    private readonly int _value;
    private readonly string _name;
}

internal sealed class eUnit : EnumType
{
    private eUnit(int value, string name): base(value, name) { }

    // operator overloads like |, amp;, ^, etc...

    internal static readonly eUnit Villager = new eUnit(0, "Villager");
    internal static readonly eUnit Warrior = new eUnit(1, "Warrior");
    internal static readonly eUnit Wizard = new eUnit(2, "Wizard");
}

internal sealed class eItem : EnumType
{
    private eItem(int value, string name): base(0, "Apple") { }

    // operator overloads like |, amp;, ^, etc...

    internal static readonly eItem Apple = new eItem(0, "Apple");
    internal static readonly eItem Steak = new eItem(1, "Steak");
    internal static readonly eItem Pizza = new eItem(2, "Pizza");
}
 

Это позволит вам затем написать:

 var myList = new List<EnumType>();
myList.Add(eUnit.Warrior);
myList.Add(eItem.Pizza);

if (myList[0] is eUnit eUnit)
{
    DoStuff();
}
 

Если бы вы заботились о дальнейшей детализации, вы могли бы также превратить эти статические поля в классы, в соответствии с:

 internal abstract class eUnit : EnumType
{
    private eUnit(int value, string name): base(value, name) { }

    // operator overloads like |, amp;, ^, etc...

    internal sealed class Villager : eUnit
    {
        private Villager(): base(0, "Villager") { }
        internal static readonly Villager _ = new Villager();
    }

    internal sealed class Warrior : eUnit
    {
        private Warrior(): base(1, "Warrior") { }
        internal static readonly Warrior _ = new Warrior();
    }

    internal sealed class Wizard : eUnit
    {
        private Wizard(): base(2, "Wizard") { }
        internal static readonly Wizard _ = new Wizard();
    }
}

internal abstract class eItem : EnumType
{
    private eItem(int value, string name): base(0, "Apple") { }

    // operator overloads like |, amp;, ^, etc...

    //...

    internal sealed class Pizza : eItem
    {
        private Pizza(): base(2, "Pizza") { }
        internal static readonly Pizza _ = new Pizza();
    }
}
 

Тогда ваш образец будет переписан как:

 var myList = new List<EnumType>();
myList.Add(eUnit.Warrior._);
myList.Add(eItem.Pizza._);

var result = myList[0] switch
{
    eUnit eUnit => eUnit switch
    {
        eUnit.Villager villager => DoVillagerStuff(villager),
        eUnit.Warrior warrior => DoWarriorStuff(warrior),
        eUnit.Wizard wizard => DoWizardStuff(wizard),
        _ => throw new InvalidOperationException("Unknonwn eItem");
    },
    eItem eItem = eItem switch
    {
        eItem.Pizza pizza => DoPizzaStuff(pizza),
        _ => throw new InvalidOperationException("Unsupported eItem")
    }
};
 

Ответ №5:

Вы могли бы взглянуть на ArrayList класс:

 public enum eUnit
{
    Villager,
    Warrior,
    Wizard,
}

public enum eVehicle
{
    Car,
    Train,
    Helicopter,
}

public enum eItem
{
    Apple,
    Steak,
    Pizza,
}

void Main()
{
    var myList = new ArrayList();
    
    myList.Add(eUnit.Warrior);
    myList.Add(eItem.Pizza);
    
    for (int i = 0; i < myList.Count; i  )
    {
        var element = myList[i];
        if (element is eUnit unit)
            Console.WriteLine($"Element at index {i} is eUnit {unit}");
        else if (element is eVehicle vehicle)
            Console.WriteLine($"Element at index {i} is eVehicle {vehicle}");
        else if (element is eItem item)
            Console.WriteLine($"Element at index {i} is eItem {item}");
        else
            Console.WriteLine($"Element at index {i} is another type");
    }
}