Is there a way to write a generic method that fills lists of different types?

101 Views Asked by At

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

There are 4 best solutions below

0
swatsonpicken On

I think maybe there's a different way of doing this. With your base SnackBase base 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:

Base Class

internal class SnackBase
{
    public string Name { get; }

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

        Name = name;
    }
}

Derived classes

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

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

VendingMachine.cs

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

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

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

    public List<SnackBase> 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<SnackBase>
        {
            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);
        }
    }
}
0
Zoltán Tamási On

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.

0
Magnus On

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>();
    }
}
0
John Alexiou On

The vending machine class only needs one list of the common type (Snack)

Snacks

public abstract class Snack
{
    protected Snack(string name)
    {
        Name = name;
    }

    public string Name { get; }
    public abstract override string ToString();
}

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

    public override string ToString() => $"Sweet({Name})";
}
public class Drink : Snack
{
    public Drink(string name) : base(name)
    {
    }

    public override string ToString() => $"Drink({Name})";
}

Vending Machine

public class VendingMachine
{
    readonly List<Snack> _snacks;

    public VendingMachine(params Snack[] snacks) => _snacks = new List<Snack>(snacks);
    public VendingMachine(IEnumerable<Snack> snacks) => _snacks = new List<Snack>(snacks);

    public IReadOnlyList<Snack> Snacks { get => _snacks; }
    public IReadOnlyList<Drink> Drinks { get => _snacks.OfType<Drink>().ToList(); }
    public IReadOnlyList<Sweet> Sweets { get => _snacks.OfType<Sweet>().ToList(); }

    public void AddDrink(string name) => _snacks.Add(new Drink(name));
    public void AddSweet(string name) => _snacks.Add(new Sweet(name));
}

Test Program

static class Program
{
    static void Main(string[] args)
    {
        var vend = new VendingMachine();

        vend.AddDrink("Joke Cola");
        vend.AddSweet("Mersa Bar");
        vend.AddDrink("Diet Goo");
        vend.AddDrink("Bronto Care");
        vend.AddSweet("Broken Tooth");

        Console.WriteLine("Vending Machine Sweets");
        foreach (var item in vend.Sweets)
        {
            Console.WriteLine(item);
        }
        Console.WriteLine();
        Console.WriteLine("Vending Machine Drinks");
        foreach (var item in vend.Drinks)
        {
            Console.WriteLine(item);
        }
    }
}

Sample Output

Vending Machine Sweets
Sweet(Mersa Bar)
Sweet(Broken Tooth)

Vending Machine Drinks
Drink(Joke Cola)
Drink(Diet Goo)
Drink(Bronto Care)