Results 1 to 7 of 7
  1. #1
    Join Date
    Sep 2016
    Posts
    14

    C# Returning multiple objects from a Method - question

    Firstly, I wish to thank people who replied to my previous posts (like florem, many thanks). I see little activity in the forum, yet I'd like to post a problem I had yesterday while writing some C# code. Perhaps people could comment and help out.

    I probably used a wrong technique. I have 3 variables (of different types), which I would like to mutate and return their different values. Imagine a rather long computation process, invloving user input.

    What I did I pass these 3 variables as parameters to a static Method and return them as a list of objects. Well... This seems inaproriate to me, as iterating over a list of objects would probably be tricky. It just looks awkward. Using objects seems somewhat wrong to me in this example. And look how I retrieve the variables. The code is not flexible. I need to remember that index 0 is for the first variable, index 1 for the second etc... a bit ugly, don't you think?

    Bottom line is, my code works. However I think I should maybe use a delegate or lambda to address the problem better? Or maybe I should use LINQ somehow, since my intention is to simply mutate data and return an effect of a computation.

    The two variables starting with 'inv' are supposed to be Inventory. Perhaps I should create a seperate Class Inventory and instantiate them? However 2 things to consider:
    1. Inventory Class would probably only have one field of type Dictionary<Product, int>
    2. creating a Constructor would be tricky as I don't know what will be the products yet.

    Could someone please consider this example and share your/their thoughts please.

    Here is a simplified code:

    Code:
        class Product
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public int Price { get; set; }
    
            public Product(int id, string name, int price)
            {
                Id = id;
                Name = name;
                Price = price;
            }
        }
    
        public static IEnumerable<object> ProcessData(List<Product> productsInput, Dictionary<Product, int> invUserInput, Dictionary<Product, int> inv2Input)
        {
            // some computation goes here, which mutates productsInput, invUserInput, inv2Input.
    
            List<object> listReturned = new List<object> { productsInput, invUserInput, inv2Input };
    
            return listReturned;
        }
    
        static void Main()
        {
            var products = new List<Product>();
            Dictionary<Product, int> invUser = new Dictionary<Product, int>();
            Dictionary<Product, int> inv2 = new Dictionary<Product, int>();
    
            IEnumerable<object> listReturned = ProcessData(products, invUser, inv2);
    
            products = (List<Product>)listReturned.ElementAt(0);
            invUser = (Dictionary<Product, int>)listReturned.ElementAt(1);
            inv2 = (Dictionary<Product, int>)listReturned.ElementAt(2);
        }
    Last edited by goldeen; 01-23-2017 at 06:43 AM.

  2. #2
    Join Date
    Aug 2010
    Posts
    160
    You are correct that this is not the best way to go about.

    Don't return a list to just select random elements of. This is not safe. I would consider something more along the lines of this.


    Code:
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Price { get; set; }
    
        public Product(int id, string name, int price)
        {
            Id = id;
            Name = name;
            Price = price;
        }
    }
    
    public class DataContext
    {
        public List<Product> Products;
        public Dictionary<Product, int> InventoryUser;
        public Dictionary<Product, int> Inventory2;
    
        public DataContext()
        {
            Products = new List<Product>();
            InventoryUser = new Dictionary<Product, int>();
            Inventory2 = new Dictionary<Product, int>();
        }
    
        public void ProcessData()
        {
            // some computation goes here
        }
    }
    
    static void Main()
    {
        DataContext data = new DataContext();
    
        // populate the data how you need
    
        data.ProcessData();
    }
    Clearly I do not know what data you are acting on, but if it is something that belongs only to that class, then the use of a static call to do work on a single type does not make sense. This action should be wrapped up in the class that it belongs to.

    For example if you have special actions to apply to the Inventories I would create a separate class to handle this too.
    Code:
    public class Inventory : Dictionary<Product, int>
    {
        public void DoSpecialAction()
        {
    
        }
    }

    Like I said though. I don't know what the specific use case is and what kind of mutations are being done. Are they all separate, or combined, or anything at all.

    Basically you want your code to not be crazily connected. Think about if you needed to add another item to your list. How much code would you need to change in order to do that in your way. What if you needed to add it to your list in another order. This can lead to a lot of bugs and while it works is frowned upon.

  3. #3
    Join Date
    Oct 2011
    Posts
    547
    @am385
    Thank you for your time and code (this and the previous post regarding delegates).
    I have to admit I had a similar attempt at explaining things but yours is, I think, clearer.

  4. #4
    Join Date
    Sep 2016
    Posts
    14
    Thanks. That makes sense, although (don't take it the wrong way) creating a DataContext class seems artificial to me. Although for sure better than the approach, which I used, which might create bugs and should be avoided.

    For reference (and since you asked about the context), here is full code. This is the famous 'Vending Machine', which I thought to write, just to practice. Any more comments?:

    As I said in the previous post: I decided against creation of Inventory class (reason: there would only be one property: stock). I also don't want to include Invantory property within Product class (reason: I feel it doesn't belong there. In database terms that would be a different table probably. Inventory isn't/shouldn't be connected to the Product per se. What if I decide to place something more than just Products in the Inventory. What if the Inventory would only have 1 piece of 1 product and we have 100 products - that means that 99 products would have a stock of 0 etc etc...).

    Instead I defined 2 variables (inv User, inv Machine) of Dictionary<Product, int> type.

    Code:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace MyVendingMachine
    {
        class Product
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public int Price { get; set; }
    
            public Product(int id, string name, int price)
            {
                Id = id;
                Name = name;
                Price = price;
            }
        }
    
        class Program
        {
            public static IEnumerable<object> ProcessData(List<Product> productsInput, Dictionary<Product, int> invUserInput, Dictionary<Product, int> invMachineInput)
            {
                var counter = 0;
                var price = 0;
                int volume = 0;
    
                while (true) // Loop for entering products.
                {
                    Console.Write("Input product name [Enter to finish]>>");
                    var input = Console.ReadLine();
                    if (input == "")
                        break;
    
                    while (true) // Loop for entering product prices.
                    {
                        Console.Write("Input product price {0}>>", input);
                        var input2 = Console.ReadLine();
                        if (!(int.TryParse(input2, out price)))
                            Console.WriteLine("Invalid price. Try again.");
                        else
                            break;
                    }
    
                    while (true) // Loop for entering product stock.
                    {
                        Console.Write("Input the stock of the product {0} in the Machine>>", input);
                        var input4 = Console.ReadLine();
                        if (!(int.TryParse(input4, out volume)))
                            Console.WriteLine("Invalid price. Try again.");
                        else
                            break;
                    }
    
                    productsInput.Add(new Product(counter, input, price));
                    invUserInput.Add(productsInput.Last(), 0);
    
                    invMachineInput.Add(productsInput.Last(), volume);
    
                    counter++;
                    Console.WriteLine();
                }
    
                List<object> listofObjects = new List<object> { productsInput, invUserInput, invMachineInput };
    
                return listofObjects;
            }
    
            public static void Engine(List<Product> productsInput, Dictionary<Product, int> invUserInput, Dictionary<Product, int> invMachineInput, int moneyInput)
            {
                while (true)
                {
                    Console.WriteLine("Money: {0}", moneyInput);
                    Console.WriteLine("MENU:");
                    Console.WriteLine("[99] Exit");
                    foreach (var item in productsInput)
                    {
                        Console.WriteLine("[{0}] Buy {1} for ${2} -- available: {3}, you have: {4}"
                            , item.Id, item.Name, item.Price, invMachineInput[item], invUserInput[item]);
                    }
    
                    var input5 = Console.ReadLine();
                    var choice = 0;
    
                    Console.Clear();
    
                    if (!(int.TryParse(input5, out choice)))
                        Console.WriteLine("Invalid price. Try again.");
                    else if (input5 == "99")
                    {
                        Console.WriteLine("You exit the program. Farewell!");
                        Environment.Exit(0);
                    }
                    else if (choice <= productsInput.Count())
                    {
                        if (moneyInput - productsInput.ElementAt(choice).Price < 0)
                            Console.WriteLine("Insufficient funds. Transaction rejected. You didn't buy {0}.", productsInput.ElementAt(choice).Name);
                        else if (invMachineInput[productsInput.ElementAt(choice)] == 0)
                            Console.WriteLine("There is no more {0} in the Machine. Transaction rejected. You didn't buy {0}.", productsInput.ElementAt(choice).Name);
                        else
                        {
                            invMachineInput[productsInput.ElementAt(choice)]--;
                            invUserInput[productsInput.ElementAt(choice)]++;
                            moneyInput = moneyInput - productsInput.ElementAt(choice).Price;
                            Console.WriteLine("You bought 1 piece of {0}.", productsInput.ElementAt(choice).Name);
                        }
    
                        if (moneyInput < productsInput.ElementAt(choice).Price)
                        {
                            Console.WriteLine("Money left: {0}. It's not enough to buy anything. You exit the program.", moneyInput);
                            Environment.Exit(0);
                        }
                        else
                            Console.WriteLine();
                    }
                    else
                    {
                        Console.Clear();
                        Console.WriteLine("Invalid input. Try again.");
                        Console.WriteLine();
                    }
                }
            }
    
    
            static void Main(string[] args)
            {
                Console.WriteLine("Welcome to Vending Machine program, OOP version.");
                Console.WriteLine();
    
                /*
                 * Step 1. User defines products and their stock in the Machine.
                 */
    
                var products = new List<Product>();
                Dictionary<Product, int> invUser = new Dictionary<Product, int>();
                Dictionary<Product, int> invMachine = new Dictionary<Product, int>();
    
                IEnumerable<object> listofObjectsRetrieved = ProcessData(products, invUser, invMachine);
    
                products = (List<Product>)listofObjectsRetrieved.ElementAt(0);
                invUser = (Dictionary<Product, int>)listofObjectsRetrieved.ElementAt(1);
                invMachine = (Dictionary<Product, int>)listofObjectsRetrieved.ElementAt(2);
    
                /*
                 * Step 2. Display product data.
                 */
    
                Console.Clear();
    
                Console.WriteLine("You input the following products:");
                Console.WriteLine();
    
                foreach (var item in products)
                    Console.WriteLine("Id: {0}, Product: {1}, price: {2}, stock in the Machine: {3}, you have: {4}"
                        , item.Id, item.Name, item.Price, invMachine[item], invUser[item]);
    
                Console.WriteLine();
                Console.WriteLine("Press any key to continue.");
                var item3 = Console.ReadKey();
    
                /*
                 * Step 3. Engine
                 */
    
                int money = 100;
    
                Console.Clear();
    
                Console.WriteLine("Welcome to Vending Machine program!");
                Console.WriteLine();
    
                Engine(products, invUser, invMachine, money);
    
                Console.ReadKey();
            }
        }
    }
    Last edited by goldeen; 01-28-2017 at 04:19 PM.

  5. #5
    Join Date
    Sep 2016
    Posts
    14
    As I researched a little more, here are 2 approaches, which I was able to work out:

    1. using Tuple
    Code:
    class Product
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public int Price { get; set; }
     
            public Product(int id, string name, int price)
            {
                Id = id;
                Name = name;
                Price = price;
            }
        }
        
        class Program
        {
            public static Tuple<List<Product>, Dictionary<Product, int>, Dictionary<Product, int>> ProcessData(
                List<Product> productsInput, Dictionary<Product, int> invUserInput, Dictionary<Product, int> inv2Input)
            {
                // some computation goes here, which mutates productsInput, invUserInput, inv2Input.
     
                var myTuple = new Tuple<List<Product>, Dictionary<Product, int>, Dictionary<Product, int>>(productsInput, invUserInput, inv2Input);
     
                return myTuple;
            }
     
            static void Main()
            {
                var products = new List<Product>();
                Dictionary<Product, int> invUser = new Dictionary<Product, int>();
                Dictionary<Product, int> inv2 = new Dictionary<Product, int>();
     
                var myTuple = ProcessData(products, invUser, inv2);
     
                products = myTuple.Item1;
                invUser = myTuple.Item2;
                inv2 = myTuple.Item3;
            }
        }
    2. creating a separate class called MyData
    Code:
    class Product
       {
           public int Id { get; set; }
           public string Name { get; set; }
           public int Price { get; set; }
     
           public Product(int id, string name, int price)
           {
               Id = id;
               Name = name;
               Price = price;
           }
       }
     
       class MyData
       {
           public List<Product> Products { get; set; }
           public Dictionary<Product, int> InvUser { get; set; }
           public Dictionary<Product, int> Inv2 { get; set; }
       }
       
       class Program
       {
           public static MyData ProcessData(MyData myDataSet1)
           {
               // some computation goes here, which mutates Products, InvUser, Inv2 - in myDataSet1 variable.
     
               return myDataSet1;
           }
     
           static void Main()
           {
               var products = new List<Product>();
               Dictionary<Product, int> invUser = new Dictionary<Product, int>();
               Dictionary<Product, int> inv2 = new Dictionary<Product, int>();
     
               var myDataSet = new MyData() { Products = products, InvUser = invUser, Inv2 = inv2 };
               ProcessData(myDataSet);
     
               products = myDataSet.Products;
               invUser = myDataSet.InvUser;
               inv2 = myDataSet.Inv2;
           }
       }

  6. #6
    Join Date
    Sep 2016
    Posts
    14
    Finally let me paste functional code. If you have any comments, please share. I'm curious to hear if this is the right approach (finally).

    I used: 1. a class MyData, 2. "ref" when accessing the Method.

    Code:
    class Product
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public int Price { get; set; }
     
            public Product(int id, string name, int price)
            {
                Id = id;
                Name = name;
                Price = price;
            }
        }
     
        class MyData
        {
            public List<Product> Products { get; set; }
            public Dictionary<Product, int> InvUser { get; set; }
            public Dictionary<Product, int> Inv2 { get; set; }
        }
        
        class Program
        {
            public static MyData ProcessData(ref MyData myDataSet)
            {
                // some computation goes here, which mutates Products, InvUser, Inv2 -- in myDataSet variable.
                myDataSet.Inv2.Add(myDataSet.Products[2], 8);
                myDataSet.Inv2[myDataSet.Products[0]] = 3;
                myDataSet.Products[2].Name = "Mirinda instead of Fanta LOL";
                myDataSet.Products[1].Price = 1;
     
                return myDataSet;
            }
     
            static void Main()
            {
                var products = new List<Product>() { new Product(0, "Coke", 2), new Product(1, "Pepsi", 5), new Product(2, "Fanta", 10) };
                Dictionary<Product, int> invUser = new Dictionary<Product, int>();
                Dictionary<Product, int> inv2 = new Dictionary<Product, int>();
                inv2.Add(products[0], 10);
                inv2.Add(products[1], 20);
     
                Console.WriteLine("There are the following drinks in the Machine: (BEFORE invoking the Method)");
                Console.WriteLine();
                foreach (var item in inv2)
                    Console.WriteLine($"Drink: {item.Key.Name}, Price: {item.Key.Price}, Stock: {item.Value}");
                Console.WriteLine("------------");
     
                var myDataSet = new MyData() { Products = products, InvUser = invUser, Inv2 = inv2 };
                ProcessData(ref myDataSet);
     
                products = myDataSet.Products;
                invUser = myDataSet.InvUser;
                inv2 = myDataSet.Inv2;
     
                Console.WriteLine("There are the following drinks in the Machine: (AFTER invoking the Method)");
                Console.WriteLine();
                foreach (var item in inv2)
                    Console.WriteLine($"Drink: {item.Key.Name}, Price: {item.Key.Price}, Stock: {item.Value}");
                Console.WriteLine("------------");
     
                Console.ReadLine();
            }
        }

  7. #7
    Join Date
    Mar 2004
    Location
    UK
    Posts
    85
    Hi, I thought I'd throw something up too.

    It may help to split things out into different components, hopefully my example isn't over-complicating what you're trying to achieve.

    Firstly I created a class to define a product itself

    Code:
    public class Product
        {
            public int Id { get; private set; }
            public string Name { get; set; }
            public decimal Price { get; set; }
    
            public Product(int id, string name, decimal price)
            {
                Id = id;
                Name = name;
                Price = price;
            }
        }
    Followed by something to wrap up Products and their stock levels

    Code:
    public class ProductStock
        {
            public Product Item { get; private set; }
            public int RemainingStock { get; private set; }
    
            public ProductStock(Product product, int stock)
            {
                Item = product;
                RemainingStock = stock > 0 ? stock : 0;
            }
    
            public void Add(int amount)
            {
                RemainingStock += amount > 0 ? amount : 0;
            }
    
            public void Deduct(int amount)
            {
                if(amount > RemainingStock)
                {
                    throw new ArgumentOutOfRangeException("amount", "cannot take more than what exists");
                }
                RemainingStock -= amount > 0 ? amount : 0;
            }
    
        }
    Next the representation of the VendingMachine, this guy is responsible for holding the inventory as well as adding and removing stock from it.

    Code:
    public class VendingMachine
        {
            public IList<ProductStock> Inventory { get; private set; }
    
            public VendingMachine()
            {
                Inventory = new List<ProductStock>();
            }
    
            public void AddStock(Product product, int amount)
            {
                if(Inventory.Any(ps => ps.Item.Id == product.Id))
                {
                    var productStock = Inventory.FirstOrDefault(ps => ps.Item.Id == product.Id);
                    productStock.Add(amount);
                }
                else
                {
                    var productStock = new ProductStock(product, amount);
                    Inventory.Add(productStock);
                }
            }
    
            public void DeductStock(Product product, int amount)
            {
                if (Inventory.Any(ps => ps.Item.Id == product.Id))
                {
                    var productStock = Inventory.FirstOrDefault(ps => ps.Item.Id == product.Id);
                    productStock.Deduct(amount);
                }
                
            }
        }

    Next up I have a repository to manage my data.


    Code:
    public class ProductRepository
        {
            private IList<Product> _dataSource;
    
            public ProductRepository()
            {
                _dataSource = new List<Product>
                {
                    new Product(0, "Coke", 1.30m),
                    new Product(1, "Pepsi", 1.10m)
                };
            }
    
            public IEnumerable<Product> Get()
            {
                return _dataSource;
            }
    
            public Product Get(int id)
            {
                return _dataSource.FirstOrDefault(p => p.Id == id);
            }
    
            public void Insert(Product product)
            {
                if(_dataSource.Any(p => p.Id == product.Id))
                {
                    throw new ArgumentException("Cannot create new product with an existing Id");
                }
    
                _dataSource.Add(product);
            }
    
            public void Update(Product product)
            {
                var toUpdate = _dataSource.FirstOrDefault(p => p.Id == product.Id);
                toUpdate.Name = product.Name;
                toUpdate.Price = product.Price;
            }
        }

    Finally the entry point. In this particular example we don't need to call the method to update the product in the repository (as the reference object we have (fanta) is the same reference we'd be changing in the update method), but if the data was contained in a database you'd want to.

    Code:
    class Program
        {
            static void Main(string[] args)
            {
    	
                var productsRepo = new ProductRepository();
                var products = productsRepo.Get();
    
                var vendingMachine = new VendingMachine();
    
                foreach(var product in products)
                {
                    vendingMachine.AddStock(product, 5);
                }
    
                
                Console.WriteLine("There are the following drinks in the Machine: (BEFORE invoking the Method)");
                Console.WriteLine();
                foreach (var productStock in vendingMachine.Inventory)
                {
                    Console.WriteLine($"Drink: {productStock.Item.Name}, Price: {productStock.Item.Price}, Stock: {productStock.RemainingStock}");
                }
                    
                Console.WriteLine("------------");
    
    
                var fanta = new Product(2, "Fanta", 1.20m);
                productsRepo.Insert(fanta);
    
                var coke = productsRepo.Get(0);
    
                vendingMachine.AddStock(fanta, 3);
                vendingMachine.DeductStock(coke, 2);
    
                fanta.Name = "Mirinda instead of Fanta LOL";
                fanta.Price = 1;
    
                productsRepo.Update(fanta);
    
                
                Console.WriteLine("There are the following drinks in the Machine: (AFTER invoking the Method)");
                Console.WriteLine();
                foreach (var productStock in vendingMachine.Inventory)
                {
                    Console.WriteLine($"Drink: {productStock.Item.Name}, Price: {productStock.Item.Price}, Stock: {productStock.RemainingStock}");
                }
                    
                Console.WriteLine("------------");
    
                Console.ReadLine();
    
            }
        }

    Hopefully there's something useful in there! If not try squeezing am385 for some more info, he certainly sounds like he knows what he's talking about :-)

    Also, let me know if you have questions on what I've got above.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •