Um, simple question, but let’s try to make it fun :D
A generic list is usually used to control a group of elements where we do not know the possible amount of these elements and so we have no way to allocate to an array or other structure (perhaps more efficient in terms of allocation). We also use lists because they are generally generic and allow us to work with any class that we create.
Also, there is the fact that many Framework functions provide list support, so they seem like a good fit.
However, they are allocated as you add elements, so they reference addresses that may not be contiguously allocated in memory, this generates fragmentation and on machines with little memory, even with free memory, you can cause an "out of memory" simply because it is so fragmented that it is impossible to allocate a new element in the available fragments.
Structures such as arrays are allocated contiguously, so they only call system Pis for memory allocation, one time. So, allocating an array may be faster than allocating a regular-sized list in the long run. Or, allocating many arrays, may not be feasible on machines with very fragmented memory.
With virtual memory, this should not happen because the OS will compensate for the lack of memory on the disk (which is much slower).
However, in the phrase, "I have a list", soon arise philosophical doubts in my mind, of the type:
- I’m gonna go linearly ?
- This list can grow to the point of making the search and modification of an element unfeasible?
- Is this list the result of a repository list method? We use Lazy load ?
- I’m being careful with the old limit of 2GB as to the size of an object ? I already blew it.
So many fun questions, but back to topic, how to change the amount of a specific product in a list?
Well, by your class, I understand as a specific product those that have a specific value in the ID property. Since your code uses ID as an integer, I suppose you cannot have null elements coming from elsewhere to compare to that property and that the value 0 is not a valid ID. (should?)
Well, there you go:
It seems to me that the real problem would be, "Given a generic list of products, as I search for and change a specific product, efficiently." Given that you seem to know how to use the assignment operator, it seems to me that your problem is to find an element efficiently in a list, given that in the lists you do not have the ordering guaranteed.
I’m not going to go into the definition of efficiency here, but let’s assume relatively common scenarios, going from a list of 5 elements to a list of 1000000 elements.
I changed your class for educational reasons (and because I want to) to have a simple builder:
public class Produto
{
public int Id { get; set; }
public string Descricao { get; set; }
public int Quantidade { get; set; }
public decimal ValorUnitario { get; set; }
public decimal ValorTotal { get; set; }
//construtor simples
public Produto(int ID, string descricao, int quantidade)
{
this.Id = ID;
this.Descricao = descricao;
this.Quantidade = quantidade;
}
}
An auxiliary method to compile a list with the amount we want:
static List<Produto> montaListaProdutos(int quantidadeProdutos)
{
//monte uma lista de produtos
List<Produto> listaProdutos = new List<Produto>();
for (int i = 1; i <= quantidadeProdutos; i++)
{
Produto p = new Produto( i , "produto " + i.ToString(), i);
listaProdutos.Add(p);
}
return listaProdutos;
}
First and most common scenario: Generic list with few elements (5 in my case):
A: There is not much to say, if the list is so small, just go through the list linearly, comparing the property until you find the element and then change what you need, example:
//Procure o produto de id 3 e altere a quantidade para 20
int idprocurado = 3;
List<Produto> minhaLista = montaListaProdutos(5);
for (int i = 0; i < minhaLista.Count ; i++)
{
if (idprocurado == minhaLista[i].Id)
{
minhaLista[i].Quantidade = 20;
};
}
In this example, since I don’t know where the ID 3 product is, I have to go through the entire list, compare it one by one and if I find the product, I change the property. This is the simplest scenario.
Evaluating this code, first I always go through the entire list and test all the elements, finding or not the right product, I always spend 5 iterations of the loop to execute the code, IE, if the list has N elements, our cost is always N, because I pass the loop through the N elements, the fewer elements I analyze to find, the better the time. Doesn’t look good, does it? But since the list is very small, that code wouldn’t be so bad. In fact, there’s no point in wasting time on such a short list.
A simple way to improve the code would be to add a break to stop searching as soon as you find it, you would avoid going through the entire list, the best case would be when the searched element was the first, the worst, when it is not in the list or is the last, Then you’d spend 1 loop on the best and 5 again on the worst. In terms of complexity, we say that we spend O(1) on the best and O(N) on the worst.
There goes the code:
int idprocurado = 3;
List<Produto> minhaLista = montaListaProdutos(5);
for (int i = 0; i < minhaLista.Count ; i++)
{
Console.WriteLine("comparando produto: " + minhaLista[i].Id.ToString());
if (idprocurado == minhaLista[i].Id)
{
minhaLista[i].Quantidade = 20;
break;
};
}
Now let’s go to a more realistic scenario, you went in the database, picked up a list of 1,000,000 products that you have no idea of the Ids and are looking for the 2500 ID guy. You don’t know the order of the elements, nor do you know if the 2500 ID is on that list, but you need to find it, and if you find change the amount to 20.
You can even use the same code, but, will have the same problem because now the list is a little bigger. Before starting the code, let’s see how I can measure and show the result of the performance changes.
To measure the time I spend on a method, I use a class called Stopwatch, from the namespace System.Diagnostics, don’t use Datetime.Now, because it can do time zone queries and end its analysis, my code looks like this:
//Procure o produto de id 3 e altere a quantidade para 20
int idprocurado = 2500;
List<Produto> minhaLista = montaListaProdutos(1000000);
//medindo o tempo
Stopwatch medidor = new Stopwatch();
medidor.Start();
//metodo avaliado
for (int i = 0; i < minhaLista.Count ; i++)
{
if (idprocurado == minhaLista[i].Id)
{
minhaLista[i].Quantidade = 20;
break;
};
}
//final do método, pare de medir o tempo
medidor.Stop();
Console.WriteLine("tempo gasto em milisegundos: " + medidor.ElapsedMilliseconds + "ms");
See that for 5 elements, with or without the break, the difference of execution is so small that the counter did not pick up - showed 0 ms - (at least on my machine). Already with 1,000,000, the counter starts showing some ms of running time, do not use Console.Writeline in performance tests, it consumes a reasonable time.
Just so you know, I did or another test with 5000 elements, looking for the guy id 2500, without the break, the code took 0 ms, with the break took 0 ms. Showing that my machine must be a rocket or that the list is too small to demonstrate anything. Already with the list of 1,000,000 searching the id 500000, showed 24 ms without the break and 12 ms with the break, probably why I am inserting in sequence and the time ends up doubling because I stop walking in half, but remember that this is a particularity of my test scenario, you have no guarantee of ordering in lists.
There are ways to sort a generic list, the implications and the way to do are in this article from Ms:
-> How to implement generic list sorting 1
Note that I used a FOR to mount my loop, I could use a FOREACH that would have no problem at all, I used the for because it made it easy to use the counter I variable in the examples and print on the screen. Now I will use a foreach to show that it would be cleaner and easier:
//medindo o tempo
Stopwatch medidorForeach = new Stopwatch();
medidorForeach.Start();
//metodo avaliado
foreach (Produto item in minhaLista)
{
if (idprocurado == item.Id)
{
item.Quantidade = 20;
break;
};
}
//final do metodo, pare de medir o tempo
medidorForeach.Stop();
Console.WriteLine("tempo gasto me milisegundos com o foreach: " + medidor.ElapsedMilliseconds + "ms");
The time in the two loop formats was the same, difference of 1 ms, that is, nothing has changed, the evaluation of the performance is still the same.
Now, you can also use parallelism (threads) to scroll through the list:
//medindo o tempo
Stopwatch medidorParalelo = new Stopwatch();
medidorParalelo.Start();
Parallel.ForEach(minhaLista,
(item, state) =>
{
if (idprocurado == item.Id)
{
item.Quantidade = 20;
state.Break();
};
});
//final do metodo, pare de medir o tempo
medidorParalelo.Stop();
Here the performance change was drastic, the method took 268 ms, opa, because it got worse if I’m using multiple threads to read the list and compare at the same time? Because parallelism requires synchronization of threads and proper scope of memory, so "allocating" a thread is not cheap. Only use if you do something heavy during the loop, like reading a photo of the product that is in a file . jpg of the disk.
Oops, so it may be that this data structure that I’m using (the list) is not a good alternative to the task of searching and changing a record, which I use then?
Before changing structure, try the Find method that receives a predicate as input, it would look like this:
//medindo o tempo
Stopwatch medidorFind = new Stopwatch();
medidorFind.Start();
//metodo avaliado
Produto result = minhaLista.Find(x => x.Id == idprocurado);
result.Quantidade = 20;
//final do metodo, pare de medir o tempo
medidorFind.Stop();
Console.WriteLine("tempo gasto me milisegundos com o find: " + medidorFind.ElapsedMilliseconds + "ms");
On my machine, the find result was 3 ms slower than directly using Foreach or For, so I recommend that you evaluate your requirements, business and performance.
See the code on . net Fiddle:
https://dotnetfiddle.net/3f6L4C
seuList[index].Quantidade = novoValor;
.index
is the position of the product you want to change the first is0
.– Roberto de Campos