Home > Software design >  Is there a way to write a generic method that fills lists of different types?
Is there a way to write a generic method that fills lists of different types?

Time:11-23

I have a parent class called Snack with subclasses Drink and Sweets. I want to store my Snacks in a "VendingMachine" Class where there is a list for each of the Products. However, I don't want to write the same method for each type of Snack. How would you write this as a generic method ?


// DRINKS LIST
List<Drink> drinks = new List<Drink>();
public List<Drink> Drinks { get => drinks; set => drinks = value; }


private void FillWithProducts <Product> (params Product[] products) where Product : Snack
 {
    Type typeParameter = typeof(Product);
    Type drink = typeof(Drink);

        foreach (Product p in products)
        {
            if (typeParameter.Equals(drink))
            {
                    Drinks.Add(p);
            }    
        }
 }

CodePudding user response:

I think maybe there's a different way of doing this. With your base Snack class and derived Drink and Sweet classes, you can fill a VendingMachine class with snacks then get the drink and sweet lists from the vending machine. The code below illustrates this:

Snack.cs

internal class Snack
{
    public string Name { get; }

    protected Snack(string name)
    {
        if (string.IsNullOrWhiteSpace(name))
            throw new ArgumentOutOfRangeException(nameof(name));

        Name = name;
    }
}

Drink.cs

internal class Drink : Snack
{
    public Drink(string name) : base(name) {}
}

Sweet.cs

internal class Sweet : Snack
{
    public Sweet(string name) : base(name) {}
}

VendingMachine.cs

internal class VendingMachine
{
    private readonly List<Snack> _snacks;

    public VendingMachine(List<Snack> snacks)
    {
        _snacks = snacks;
    }

    public List<Snack> GetDrinks()
    {
        return _snacks.Where(s => s.GetType().Name == nameof(Drink)).ToList();
    }

    public List<Snack> GetSweets()
    {
        return _snacks.Where(s => s.GetType().Name == nameof(Sweet)).ToList();
    }
}

Program.cs

internal static class Program
{
    public static void Main()
    {
        var snacks = new List<Snack>
            {
                new Drink("Coke"),
                new Sweet("Snickers"),
                new Drink("Pepsi"),
                new Sweet("Mars Bar"),
                new Drink("7 Up"),
                new Sweet("Reece's Pieces")
            };

        var vendingMachine = new VendingMachine(snacks);

        Console.WriteLine("Drinks");
        Console.WriteLine("------");
        var drinks = vendingMachine.GetDrinks();
        foreach (var drink in drinks)
        {
            Console.WriteLine(drink.Name);
        }

        Console.WriteLine("Sweets");
        Console.WriteLine("------");
        var sweets = vendingMachine.GetSweets();
        foreach (var sweet in sweets)
        {
            Console.WriteLine(sweet.Name);
        }
    }
}

CodePudding user response:

If you really need to store each kinds of products in theair own list, you can use a dynamically populated dictionary where the key is the type, something like this.

private readonly Dictionary<Type, List<Product>> storeByType = new();

public List<Drink> Drinks => (List<Drink>)this.storeByType[typeof(Drink)]

private void FillWithProducts<Product>(params Product[] products) where Product : Snack
{
    foreach (Product p in products)
    {
        var key = p.GetType();
        if (!this.storeByType.ContainsKey(key)) {
            // ... add new List<T> instantiated by reflection
            // use MakeGenericType   Activator.CreateInstance for example
        }

        // cast to the non-generic interface
        var list = (IList)this.storeByType[key];
        list.Add(p);
    }
}

Note, that the code is just present as an example to demonstrate the idea, missing many checks and safety, and might not even work as is.

CodePudding user response:

I would keep a dictionary inside the VendingMachine that holds the snacks of different types with the type as the key. By doing so you avoid having to search a list with mixed types every time you want to fetch the items.

static void Main(string[] args)
{
    var m = new VendingMachine();
    m.AddRange(new Drink(), new Drink());
    m.AddRange(new Sweet());

    var drinks = m.Fetch<Drink>();
    var sweets = m.Fetch<Sweet>();
}

public class VendingMachine
{
    private readonly Dictionary<Type, List<Snack>> _snacks = new();

    public void AddRange<T>(params T[] snacks) where T : Snack
    {
        var type = typeof(T);
        if (_snacks.TryGetValue(type, out var existingSnacks))
            existingSnacks.AddRange(snacks);
        else
            _snacks.Add(type, new List<Snack>(snacks));
    }

    public List<T> Fetch<T>() where T : Snack
    {
        if (_snacks.TryGetValue(typeof(T), out var existingSnacks))
            return new List<T>(existingSnacks.Cast<T>());
        return new List<T>();
    }
}
  • Related