Matriz versus Lista <T>: Quando usar qual?

594
MyClass[] array;
List<MyClass> list;

Quais são os cenários em que um é preferível ao outro? E porque?

Frederick The Fool
fonte
9
Matrizes são bastante obsoletas, como visto em uma discussão popular aqui. Também indicado aqui , e pelo nosso anfitrião no blog .
gimel
9
Se não me engano, a lista <> tem uma matriz como estrutura interna. Sempre que a matriz interna é preenchida, basta copiar o conteúdo para uma matriz com o dobro do tamanho (ou algumas outras vezes constantes do tamanho atual). pt.wikipedia.org/wiki/Dynamic_array
Ykok
Ykok: O que você diz parece certo, encontrei o código fonte da Lista <> aqui .
22414 Carol
19
@gimel Argumentando que matrizes são obsoletos é talvez um pouco ousada
awdz9nld

Respostas:

580

É raro, na realidade, que você queira usar uma matriz. Definitivamente, use a List<T>qualquer momento que desejar adicionar / remover dados, pois o redimensionamento de matrizes é caro. Se você sabe que os dados são de tamanho fixo e deseja otimizar por algum motivo muito específico (após o benchmarking), uma matriz pode ser útil.

List<T>oferece muito mais funcionalidade do que uma matriz (embora o LINQ aumente um pouco) e é quase sempre a escolha certa. Exceto pelos paramsargumentos, é claro. ;-p

Como um contador - List<T>é unidimensional; onde você tem matrizes retangulares (etc) como int[,]ou string[,,]- mas existem outras maneiras de modelar esses dados (se necessário) em um modelo de objeto.

Veja também:

Dito isto, faço muito uso de matrizes no meu projeto protobuf-net ; inteiramente para desempenho:

  • faz muitas mudanças de bits; portanto, a byte[]é praticamente essencial para a codificação;
  • Eu uso um byte[]buffer local rotativo que preencho antes de enviar para o fluxo subjacente (e vv); mais rápido que BufferedStreametc;
  • ele usa internamente um modelo de objetos baseado em matriz (em Foo[]vez de List<Foo>), pois o tamanho é fixo depois de criado e precisa ser muito rápido.

Mas isso é definitivamente uma exceção; para o processamento geral da linha de negócios, a List<T>vence sempre.

Marc Gravell
fonte
8
O argumento sobre o redimensionamento é totalmente válido. No entanto, as pessoas preferem listas mesmo quando não é necessário redimensionar. Para este último caso, existe um argumento sólido e lógico ou nada mais é do que "matrizes estão fora de moda"?
Frederick o tolo
6
"Definitivamente, use uma Lista <T> sempre que quiser adicionar / remover dados, pois o redimensionamento de matrizes é caro." Lista <T> usa uma matriz internamente. Você estava pensando em LinkedList <T>?
dan-gph
14
Mais recursos == mais complexos == não são bons, a menos que você precise desses recursos. Essa resposta basicamente lista as razões pelas quais os array são melhores, mas tira a conclusão oposta.
Eamon Nerbonne
12
@EamonNerbonne, se você não estiver usando esses recursos, posso garantir que eles não vão machucá-lo ... mas: na minha experiência, o número de coleções que nunca precisam de mutação é muito menor do que aquelas que são alterado
Marc Gravell
7
@ MarcGravell: isso depende do seu estilo de codificação. Na minha experiência, praticamente nenhuma coleção é mutada. Isso é; as coleções são recuperadas do banco de dados ou construídas a partir de alguma fonte, mas o processamento adicional sempre é feito recriando uma nova coleção (por exemplo, mapa / filtro etc.). Mesmo onde a mutação conceitual é necessária, tende a ser mais simples gerar apenas uma nova coleção. Eu apenas modifico uma coleção como uma otimização de desempenho, e essas otimizações tendem a ser altamente locais e a não expor a mutação aos consumidores da API.
Eamon Nerbonne
121

Realmente, apenas responder para adicionar um link que me surpreende ainda não foi mencionado: a entrada de Eric Lippert no blog "Arrays considerados um tanto prejudiciais".

Você pode julgar pelo título que está sugerindo o uso de coleções sempre que possível - mas, como Marc indica corretamente, há muitos lugares em que uma matriz realmente é a única solução prática.

Jon Skeet
fonte
2
Finalmente comecei a ler isso mais de 3 anos depois haha. Bom artigo então, bom artigo agora. :)
Spencer Ruport 20/04/12
21

Não obstante as outras respostas recomendadas List<T>, você desejará usar matrizes ao manipular:

  • dados de bitmap da imagem
  • outras estruturas de dados de baixo nível (isto é, protocolos de rede)
Alnitak
fonte
1
Por que para protocolos de rede? Você não prefere usar estruturas personalizadas aqui e fornecer a eles um serializador especial ou um layout de memória explícito? Além disso, o que é contra o uso List<T>de uma matriz aqui em vez de uma matriz de bytes?
Konrad Rudolph
8
@ Konrad - Bem, para começar, Stream.Read e Stream.Write trabalho com byte [], como faz Encoding etc ...
Marc Gravell
12

A menos que você esteja realmente preocupado com o desempenho, e com isso quero dizer: "Por que você está usando .Net em vez de C ++?" você deve ficar com a lista <>. É mais fácil de manter e faz todo o trabalho sujo de redimensionar uma matriz nos bastidores para você. (Se necessário, a List <> é bastante inteligente sobre a escolha de tamanhos de matriz, portanto, não é necessário normalmente.)

Spencer Ruport
fonte
15
"Por que você está usando .Net em vez de C ++?" XNA
Bengt
6

As matrizes devem ser usadas preferencialmente à lista quando a imutabilidade da coleção em si faz parte do contrato entre o código do cliente e do provedor (não necessariamente imutabilidade dos itens da coleção) E quando IEnumerable não é adequado.

Por exemplo,

var str = "This is a string";
var strChars = str.ToCharArray();  // returns array

É claro que a modificação de "strChars" não altera o objeto "str" ​​original, independentemente do conhecimento no nível de implementação do tipo subjacente de "str".

Mas suponha que

var str = "This is a string";
var strChars = str.ToCharList();  // returns List<char>
strChars.Insert(0, 'X');

Nesse caso, não está claro apenas esse trecho de código se o método insert irá ou não alterar o objeto "str" ​​original. Ele requer conhecimento em nível de implementação de String para fazer essa determinação, o que quebra a abordagem Design por Contrato. No caso de String, não é grande coisa, mas pode ser grande em quase todos os outros casos. Definir a lista como somente leitura ajuda, mas resulta em erros em tempo de execução, não em tempo de compilação.

Herman Schoenfeld
fonte
Eu sou relativamente novo em C #, mas não está claro para mim por que retornar uma lista sugeriria mutabilidade dos dados originais de uma maneira que o retorno de uma matriz não o faria. Eu teria pensado que um método cujo nome começa Tocria um objeto que não tem capacidade de modificar a instância original, ao contrário do strChars as char[]que, se válido, sugeriria que você agora pode modificar o objeto original.
Tim MB
@TimMB Existe a imutabilidade da coleção (não é possível adicionar ou remover itens) e a imutabilidade dos itens da coleção. Eu estava me referindo ao último, enquanto você provavelmente está confundindo os dois. O retorno de uma matriz garante ao cliente que não é possível adicionar / remover itens. Se isso acontecer, ele re-aloca a matriz e garante que não afetará o original. Ao retornar uma lista, essas garantias não são feitas e o original pode ser afetado (depende da implementação). Alterar itens na coleção (matriz ou lista) pode afetar o original, se o tipo do item não for uma estrutura.
Herman Schoenfeld
Obrigado pelo esclarecimento. Ainda estou confuso (provavelmente porque venho do mundo C ++). Se strinternamente usar uma matriz e ToCharArrayretornar uma referência a essa matriz, o cliente poderá sofrer stralterações alterando os elementos dessa matriz, mesmo que o tamanho permaneça fixo. No entanto, você escreve 'É claro que a modificação de "strChars" não mudará o objeto "str" ​​original'. O que estou perdendo aqui? Pelo que pude ver, em ambos os casos, o cliente pode ter acesso à representação interna e, independentemente do tipo, isso permitiria algum tipo de mutação.
Tim MB
3

Se eu sei exatamente quantos elementos Vou necessidade, digo que preciso de 5 elementos e apenas nunca 5 elementos, então eu usar uma matriz. Caso contrário, eu apenas uso uma lista <T>.

smack0007
fonte
1
Por que você não usaria uma lista <T> no caso de saber o número de elementos?
Oliver
3

Na maioria das vezes, usar a Listseria suficiente. A Listusa uma matriz interna para manipular seus dados e redimensiona automaticamente a matriz ao adicionar mais elementos à Listsua capacidade atual, o que facilita a utilização do que uma matriz, na qual você precisa conhecer a capacidade com antecedência.

Consulte http://msdn.microsoft.com/en-us/library/ms379570(v=vs.80).aspx#datastructures20_1_topic5 para obter mais informações sobre listas em C # ou apenas descompilar System.Collections.Generic.List<T>.

Se você precisar de dados multidimensionais (por exemplo, usando uma matriz ou em programação gráfica), provavelmente deverá usar um array.

Como sempre, se a memória ou o desempenho são um problema, meça-o! Caso contrário, você pode estar fazendo suposições falsas sobre o código.

Sune Rievers
fonte
1
Olá, você poderia explicar por que "O tempo de pesquisa de uma lista seria O (n)" é verdadeiro? Até onde eu sei, a List <T> usa a matriz nos bastidores.
libélula
1
@dragonfly você está totalmente certo. Fonte . Na época, assumi que a implementação usava ponteiros, mas desde então aprendi o contrário. No link acima: 'A recuperação do valor dessa propriedade é uma operação O (1); definir a propriedade também é uma operação O (1). '
Sune Rievers 07/08/2012
2

Matrizes vs. Listas é um problema clássico de manutenção versus desempenho. A regra geral que quase todos os desenvolvedores seguem é que você deve atirar nos dois, mas quando eles entrarem em conflito, escolha a manutenção em vez do desempenho. A exceção a essa regra é quando o desempenho já provou ser um problema. Se você levar esse princípio para Arrays vs. Listas, então o que você recebe é o seguinte:

Use listas fortemente digitadas até encontrar problemas de desempenho. Se você encontrar um problema de desempenho, tome uma decisão sobre se descartar as matrizes beneficiará sua solução com desempenho mais do que prejudicá-la em termos de manutenção.

Melbourne Developer
fonte
1

Outra situação ainda não mencionada é quando alguém terá um grande número de itens, cada um dos quais consiste em um conjunto fixo de variáveis ​​relacionadas, mas independentes, coladas (por exemplo, as coordenadas de um ponto ou os vértices de um triângulo 3d). Uma matriz de estruturas de campo exposto permitirá que seus elementos sejam eficientemente modificados "no lugar" - algo que não é possível em nenhum outro tipo de coleção. Como uma matriz de estruturas mantém seus elementos consecutivamente na RAM, os acessos sequenciais aos elementos da matriz podem ser muito rápidos. Nas situações em que o código precisará fazer muitas passagens seqüenciais através de uma matriz, uma matriz de estruturas pode superar uma matriz ou outra coleção de referências a objetos de classe por um fator de 2: 1; mais longe,

Embora as matrizes não sejam redimensionáveis, não é difícil fazer com que o código armazene uma referência de matriz junto com o número de elementos em uso e substitua a matriz por uma maior, conforme necessário. Como alternativa, pode-se facilmente escrever código para um tipo que se comporte de maneira semelhante a um, List<T>mas exponha seu repositório, permitindo assim que se diga um MyPoints.Add(nextPoint);ou outro MyPoints.Items[23].X += 5;. Observe que o último não lançaria necessariamente uma exceção se o código tentasse acessar além do final da lista, mas o uso seria conceitualmente bastante semelhante ao List<T>.

supercat
fonte
O que você descreveu é uma Lista <>. Há um indexador para que você possa acessar diretamente a matriz subjacente, e a Lista <> manterá o tamanho para você.
Carl
@Carl: Dado Point[] arr;, por exemplo , é possível que o código diga, por exemplo arr[3].x+=q;. Usando List<Point> list, por exemplo , seria necessário dizer Point temp=list[3]; temp.x+=q; list[3]=temp;. Seria útil se List<T>tivesse um método Update<TP>(int index, ActionByRefRef<T,TP> proc, ref TP params). e compiladores poderia se transformar list[3].x+=q;em {list.Update(3, (ref int value, ref int param)=>value+=param, ref q);mas tal recurso existe.
supercat
Boas notícias. Funciona. list[0].X += 3;adicionará 3 à propriedade X do primeiro elemento da lista. E listé um List<Point>e Pointé uma classe com X e Y Propriedades
Carl
1

As listas no .NET são wrappers sobre matrizes e usam uma matriz internamente. A complexidade de tempo das operações nas listas é a mesma que seria com as matrizes, no entanto, há um pouco mais de sobrecarga com toda a funcionalidade / facilidade de uso das listas (como redimensionamento automático e os métodos que acompanham a classe da lista). Basicamente, eu recomendaria o uso de listas em todos os casos, a menos que haja um motivo convincente para não fazê-lo, como se você precisar escrever um código extremamente otimizado ou se estiver trabalhando com outro código criado em torno de matrizes.

iliketocode
fonte
0

Em vez de fazer uma comparação dos recursos de cada tipo de dados, acho que a resposta mais pragmática é "as diferenças provavelmente não são tão importantes para o que você precisa realizar, principalmente porque as duas implementam IEnumerable, siga as convenções populares e use um Listaté que você tenha um motivo para não fazê-lo, nesse momento você provavelmente terá seu motivo para usar uma matriz sobre a List. "

Na maioria das vezes, no código gerenciado, você prefere que as coleções sejam o mais fácil de trabalhar, ao invés de se preocupar com as otimizações.

moarboilerplate
fonte
0

Eles podem ser impopulares, mas eu sou fã de Arrays em projetos de jogos. - A velocidade de iteração pode ser importante em alguns casos, pois cada matriz tem uma sobrecarga significativamente menor se você não estiver fazendo muito por elemento - Adicionar e remover não é tão difícil com as funções auxiliares - É mais lento, mas nos casos em que você a constrói apenas uma vez pode não importar - Na maioria dos casos, menos memória extra é desperdiçada (apenas realmente significativa com matrizes de estruturas) - Pouco menos lixo e ponteiros e perseguição de ponteiros

Dito isto, eu uso a List com muito mais frequência do que Arrays na prática, mas cada um tem seu lugar.

Seria bom se List fosse um tipo incorporado, para que eles pudessem otimizar a sobrecarga do wrapper e da enumeração.


fonte
0

Preencher uma lista é mais fácil que uma matriz. Para matrizes, é necessário saber o tamanho exato dos dados, mas para listas, o tamanho dos dados pode ser qualquer. E você pode converter uma lista em uma matriz.

List<URLDTO> urls = new List<URLDTO>();

urls.Add(new URLDTO() {
    key = "wiki",
    url = "https://...",
});

urls.Add(new URLDTO()
{
    key = "url",
    url = "http://...",
});

urls.Add(new URLDTO()
{
    key = "dir",
    url = "https://...",
});

// convert a list into an array: URLDTO[]
return urls.ToArray();
Bimal Poudel
fonte
0

Como ninguém menciona: em C #, uma matriz é uma lista. MyClass[]e List<MyClass>ambos implementam IList<MyClass>. (por exemplo, void Foo(IList<int> foo)pode ser chamado como Foo(new[] { 1, 2, 3 })ouFoo(new List<int> { 1, 2, 3 }) )

Portanto, se você estiver escrevendo um método que aceite a List<MyClass>como argumento, mas use apenas um subconjunto de recursos, poderá declarar comoIList<MyClass> para conveniência dos chamadores.

Detalhes:

snipsnipsnip
fonte
"Em C #, uma matriz é uma lista" Isso não é verdade; uma matriz não é uma List, apenas implementa a IListinterface.
Rufus L
-1

Depende completamente dos contextos em que a estrutura de dados é necessária. Por exemplo, se você estiver criando itens para serem usados ​​por outras funções ou serviços usando a Lista, é a maneira perfeita de realizá-lo.

Agora, se você possui uma lista de itens e deseja apenas exibi-los, digamos que, em uma matriz de páginas da Web, é o contêiner que você precisa usar.

sajidnizami
fonte
1
Se você possui uma lista de itens e deseja apenas exibi-los, o que há de errado em usar a lista que você já possui? O que uma matriz ofereceria aqui?
Marc Gravell
1
E para "criar itens para serem usados ​​por outras funções ou serviços", na verdade, eu prefiro um bloco iterador com IEnumerable<T>- então eu posso transmitir objetos ao invés de armazená-los em buffer.
Marc Gravell
-1

Vale a pena mencionar sobre a capacidade de fazer casting no local.

interface IWork { }
class Foo : IWork { }

void Test( )
{
    List<Foo> bb = new List<Foo>( );
    // Error: CS0029 Cannot implicitly convert type 'System.Collections.Generic.List<Foo>' to 'System.Collections.Generic.List<IWork>'
    List<IWork> cc = bb; 

    Foo[] bbb = new Foo[4];
    // Fine
    IWork[] ccc = bbb;
}

Portanto, a matriz oferece um pouco mais de flexibilidade quando usada no tipo ou argumento de retorno para funções.

IWork[] GetAllWorks( )
{
    List<Foo> fooWorks = new List<Foo>( );
    return fooWorks.ToArray( ); // Fine
}

void ExecuteWorks( IWork[] works ) { } // Also accept Foo[]
Wappenull
fonte
Você deve observar que a variação da matriz está quebrada .
mcarton 19/04